926 lines
30 KiB
Plaintext
926 lines
30 KiB
Plaintext
|
Assembly In One Step
|
||
|
|
||
|
RTK, last update: 23-Jul-97
|
||
|
|
||
|
A brief guide to programming the 6502 in assembly language. It will
|
||
|
introduce the 6502 architecture, addressing modes, and instruction set. No
|
||
|
prior assembly language programming is assumed, however it is assumed that
|
||
|
you are somewhat familiar with hexadecimal numbers. Programming examples
|
||
|
are given at the end. Much of this material comes from 6502 Software Design
|
||
|
by Leo Scanlon, Blacksburg, 1980.
|
||
|
|
||
|
------------------------------------------------------------------------
|
||
|
|
||
|
The 6502 Architecture
|
||
|
---------------------
|
||
|
|
||
|
The 6502 is an 8-bit microprocessor that follows the memory oriented
|
||
|
design philosophy of the Motorola 6800. Several engineers left
|
||
|
Motorola and formed MOS Technology which introduced the 6502 in 1975.
|
||
|
The 6502 gained in popularity because of it's low price and became the
|
||
|
heart of several early personal computers including the Apple II,
|
||
|
Commodore 64, and Atari 400 and 800.
|
||
|
|
||
|
|
||
|
Simplicity is key
|
||
|
-----------------
|
||
|
|
||
|
The 6502 handles data in its registers, each of which holds one byte
|
||
|
(8-bits) of data. There are a total of three general use and two special
|
||
|
purpose registers:
|
||
|
|
||
|
|
||
|
accumulator (A) - Handles all arithmetic and logic. The real heart
|
||
|
of the system.
|
||
|
|
||
|
X and Y - General purpose registers with limited abilities.
|
||
|
|
||
|
S - Stack pointer.
|
||
|
|
||
|
P - Processor status. Holds the result of tests
|
||
|
and flags.
|
||
|
|
||
|
|
||
|
Stack Pointer
|
||
|
-------------
|
||
|
|
||
|
When the microprocessor executes a JSR (Jump to SubRoutine)
|
||
|
instruction it needs to know where to return when finished. The 6502
|
||
|
keeps this information in low memory from $0100 to $01FF and uses the
|
||
|
stack pointer as an offset. The stack grows down from $01FF and makes
|
||
|
it possible to nest subroutines up to 128 levels deep. Not a problem
|
||
|
in most cases.
|
||
|
|
||
|
|
||
|
Processor Status
|
||
|
----------------
|
||
|
|
||
|
The processor status register is not directly accessible by any 6502
|
||
|
instruction. Instead, there exist numerous instructions that test the
|
||
|
bits of the processor status register. The flags within the register
|
||
|
are:
|
||
|
|
||
|
|
||
|
bit -> 7 0
|
||
|
+---+---+---+---+---+---+---+---+
|
||
|
| N | V | | B | D | I | Z | C | <-- flag, 0/1 = reset/set
|
||
|
+---+---+---+---+---+---+---+---+
|
||
|
|
||
|
|
||
|
N = NEGATIVE. Set if bit 7 of the accumulator is set.
|
||
|
|
||
|
V = OVERFLOW. Set if the addition of two like-signed numbers or the
|
||
|
subtraction of two unlike-signed numbers produces a result
|
||
|
greater than +127 or less than -128.
|
||
|
|
||
|
B = BRK COMMAND. Set if an interrupt caused by a BRK, reset if
|
||
|
caused by an external interrupt.
|
||
|
|
||
|
D = DECIMAL MODE. Set if decimal mode active.
|
||
|
|
||
|
I = IRQ DISABLE. Set if maskable interrupts are disabled.
|
||
|
|
||
|
Z = ZERO. Set if the result of the last operation (load/inc/dec/
|
||
|
add/sub) was zero.
|
||
|
|
||
|
C = CARRY. Set if the add produced a carry, or if the subtraction
|
||
|
produced a borrow. Also holds bits after a logical shift.
|
||
|
|
||
|
|
||
|
Accumulator
|
||
|
-----------
|
||
|
|
||
|
The majority of the 6502's business makes use of the accumulator. All
|
||
|
addition and subtraction is done in the accumulator. It also handles
|
||
|
the majority of the logical comparisons (is A > B ?) and logical bit
|
||
|
shifts.
|
||
|
|
||
|
|
||
|
X and Y
|
||
|
-------
|
||
|
|
||
|
These are index registers often used to hold offsets to memory
|
||
|
locations. They can also be used for holding needed values. Much of
|
||
|
their use lies in supporting some of the addressing modes.
|
||
|
|
||
|
|
||
|
|
||
|
Addressing Modes
|
||
|
----------------
|
||
|
|
||
|
The 6502 has 13 addressing modes, or ways of accessing memory. The 65C02
|
||
|
introduces two additional modes.
|
||
|
|
||
|
They are:
|
||
|
|
||
|
|
||
|
+---------------------+--------------------------+
|
||
|
| mode | assembler format |
|
||
|
+=====================+==========================+
|
||
|
| Immediate | #aa |
|
||
|
| Absolute | aaaa |
|
||
|
| Zero Page | aa | Note:
|
||
|
| Implied | |
|
||
|
| Indirect Absolute | (aaaa) | aa = 2 hex digits
|
||
|
| Absolute Indexed,X | aaaa,X | as $FF
|
||
|
| Absolute Indexed,Y | aaaa,Y |
|
||
|
| Zero Page Indexed,X | aa,X | aaaa = 4 hex
|
||
|
| Zero Page Indexed,Y | aa,Y | digits as
|
||
|
| Indexed Indirect | (aa,X) | $FFFF
|
||
|
| Indirect Indexed | (aa),Y |
|
||
|
| Relative | aaaa | Can also be
|
||
|
| Accumulator | A | assembler labels
|
||
|
+---------------------+--------------------------+
|
||
|
|
||
|
(Table 2-3. _6502 Software Design_, Scanlon, 1980)
|
||
|
|
||
|
|
||
|
Immediate Addressing
|
||
|
--------------------
|
||
|
|
||
|
The value given is a number to be used immediately by the
|
||
|
instruction. For example, LDA #$99 loads the value $99 into the
|
||
|
accumulator.
|
||
|
|
||
|
|
||
|
Absolute Addressing
|
||
|
-------------------
|
||
|
|
||
|
The value given is the address (16-bits) of a memory location that
|
||
|
contains the 8-bit value to be used. For example, STA $3E32 stores
|
||
|
the present value of the accumulator in memory location $3E32.
|
||
|
|
||
|
|
||
|
Zero Page Addressing
|
||
|
--------------------
|
||
|
|
||
|
The first 256 memory locations ($0000-00FF) are called "zero page". The
|
||
|
next 256 instructions ($0100-01FF) are page 1, etc. Instructions
|
||
|
making use of the zero page save memory by not using an extra $00 to
|
||
|
indicate the high part of the address. For example,
|
||
|
|
||
|
LDA $0023 -- works but uses an extra byte
|
||
|
LDA $23 -- the zero page address
|
||
|
|
||
|
|
||
|
Implied Addressing
|
||
|
------------------
|
||
|
|
||
|
Many instructions are only one byte in length and do not reference
|
||
|
memory. These are said to be using implied addressing. For example,
|
||
|
|
||
|
CLC -- Clear the carry flag
|
||
|
DEX -- Decrement the X register by one
|
||
|
TYA -- Transfer the Y register to the accumulator
|
||
|
|
||
|
|
||
|
Indirect Absolute Addressing
|
||
|
----------------------------
|
||
|
|
||
|
Only used by JMP (JuMP). It takes the given address and uses it as a
|
||
|
pointer to the low part of a 16-bit address in memory, then jumps to
|
||
|
that address. For example,
|
||
|
|
||
|
JMP ($2345) -- jump to the address in $2345 low and $2346 high
|
||
|
|
||
|
So if $2345 contains $EA and $2346 contains $12 then the next
|
||
|
instruction executed is the one stored at $12EA. Remember, the
|
||
|
6502 puts its addresses in low/high format.
|
||
|
|
||
|
|
||
|
Absolute Indexed Addressing
|
||
|
---------------------------
|
||
|
|
||
|
The final address is found by taking the given address as a base and
|
||
|
adding the current value of the X or Y register to it as an offset. So,
|
||
|
|
||
|
LDA $F453,X where X contains 3
|
||
|
|
||
|
Load the accumulator with the contents of address $F453 + 3 = $F456.
|
||
|
|
||
|
|
||
|
Zero Page Indexed Addressing
|
||
|
----------------------------
|
||
|
|
||
|
Same as Absolute Indexed but the given address is in the zero page
|
||
|
thereby saving a byte of memory.
|
||
|
|
||
|
|
||
|
Indexed Indirect Addressing
|
||
|
---------------------------
|
||
|
|
||
|
Find the 16-bit address starting at the given location plus the
|
||
|
current X register. The value is the contents of that address. For
|
||
|
example,
|
||
|
|
||
|
LDA ($B4,X) where X contains 6
|
||
|
|
||
|
gives an address of $B4 + 6 = $BA. If $BA and $BB contain $12 and
|
||
|
$EE respectively, then the final address is $EE12. The value at
|
||
|
location $EE12 is put in the accumulator.
|
||
|
|
||
|
|
||
|
Indirect Indexed Addressing
|
||
|
---------------------------
|
||
|
|
||
|
Find the 16-bit address contained in the given location ( and the one
|
||
|
following). Add to that address the contents of the Y register.
|
||
|
Fetch the value stored at that address. For example,
|
||
|
|
||
|
LDA ($B4),Y where Y contains 6
|
||
|
|
||
|
If $B4 contains $EE and $B5 contains $12 then the value at memory
|
||
|
location $12EE + Y (6) = $12F4 is fetched and put in the accumulator.
|
||
|
|
||
|
|
||
|
Relative Addressing
|
||
|
-------------------
|
||
|
|
||
|
The 6502 branch instructions use relative addressing. The next byte
|
||
|
is a signed offset from the current address, and the net sum is the
|
||
|
address of the next instruction executed. For example,
|
||
|
|
||
|
BNE $7F (branch on zero flag reset)
|
||
|
|
||
|
will add 127 to the current program counter (address to execute) and
|
||
|
start executing the instruction at that address. SImilarly,
|
||
|
|
||
|
BEQ $F9 (branch on zero flag set)
|
||
|
|
||
|
will add a -7 to the current program counter and start execution at
|
||
|
the new program counter address.
|
||
|
|
||
|
Remember, if one treats the highest bit (bit 7) of a byte as a sign (0
|
||
|
= positive, 1 = negative) then it is possible to have numbers in the
|
||
|
range -128 ($80) to +127 (7F). So, if the high bit is set, i.e. the
|
||
|
number is > $7F, it is a negative branch. How far is the branch? If
|
||
|
the value is < $80 (positive) it is simply that many bytes. If the
|
||
|
value is > $7F (negative) then it is the 2's compliment of the given
|
||
|
value in the negative direction.
|
||
|
|
||
|
2's compilment
|
||
|
--------------
|
||
|
|
||
|
The 2's compilment of a number is found by switching all the bits
|
||
|
from 0 -> 1 and 1 -> 0, then adding 1. So,
|
||
|
|
||
|
$FF = 1111 1111 <-- original
|
||
|
0000 0000 <-- 1's compliment
|
||
|
+ 1
|
||
|
---------
|
||
|
0000 0001 <-- 2's compliment, therefore $FF = -1
|
||
|
|
||
|
Note that QForth uses this for numbers greater than 32768 so that
|
||
|
65535 = -1 and 32768 = -32768.
|
||
|
|
||
|
In practice, the assembly language programmer uses a label and the
|
||
|
assembler takes care of the actual computation. Note that branches
|
||
|
can only be to addresses within -128 to +127 bytes from the present
|
||
|
address. The 6502 does not allow branches to an absolute address.
|
||
|
|
||
|
|
||
|
Accumulator Addressing
|
||
|
----------------------
|
||
|
|
||
|
Like implied addressing, the object of the instruction is the
|
||
|
accumulator and need not be specified.
|
||
|
|
||
|
|
||
|
|
||
|
The 6502 Instruction Set
|
||
|
------------------------
|
||
|
|
||
|
There are 56 instructions in the 6502, and more in the 65C02. Many
|
||
|
instructions make use of more than one addressing mode and each
|
||
|
instruction/addressing mode combination has a particular hexadecimal
|
||
|
opcode that specifies it exactly. So,
|
||
|
|
||
|
A9 = LDA #$aa Immediate addressing mode load of accumulator
|
||
|
AD = LDA $aaaa Absolute addressing mode load of accumulator
|
||
|
etc.
|
||
|
|
||
|
|
||
|
Some 6502 instructions make use of bitwise logic. This includes AND,
|
||
|
OR, and EOR (Exclusive-OR). The tables below illustrate the effects
|
||
|
of these operations:
|
||
|
|
||
|
AND 1 1 -> 1 "both"
|
||
|
1 0 -> 0
|
||
|
0 1 -> 0
|
||
|
0 0 -> 0
|
||
|
|
||
|
OR 1 1 -> 1 "either one or both"
|
||
|
1 0 -> 1
|
||
|
0 1 -> 1
|
||
|
0 0 -> 0
|
||
|
|
||
|
EOR 1 1 -> 0 "one or the other but not both"
|
||
|
1 0 -> 1
|
||
|
0 1 -> 1
|
||
|
0 0 -> 0
|
||
|
|
||
|
Therefore, $FF AND $0F = $0F since,
|
||
|
|
||
|
1111 1111
|
||
|
and 0000 1111
|
||
|
---------
|
||
|
0000 1111 = $0F
|
||
|
|
||
|
|
||
|
AND is useful for masking bits. For example, to mask the high order
|
||
|
bits of a value AND with $0F:
|
||
|
|
||
|
$36 AND $0F = $06
|
||
|
|
||
|
OR is useful for setting a particular bit:
|
||
|
|
||
|
$80 OR $08 = $88
|
||
|
|
||
|
since 1000 0000 ($80)
|
||
|
0000 1000 ($08)
|
||
|
or ---------
|
||
|
1000 1000 ($88)
|
||
|
|
||
|
EOR is useful for flipping bits:
|
||
|
|
||
|
$AA EOR $FF = $55
|
||
|
|
||
|
since 1010 1010 ($AA)
|
||
|
1111 1111 ($FF)
|
||
|
eor ---------
|
||
|
0101 0101 ($55)
|
||
|
|
||
|
|
||
|
Other 6502 instructions shift bits to the right or the left or rotate
|
||
|
them right or left. Note that shifting to the left by one bit is the
|
||
|
same as multipling by 2 and that shifting right by one bit is the same
|
||
|
as dividing by 2.
|
||
|
|
||
|
|
||
|
The 6502 instructions fall naturally into 10 groups with two odd-ball
|
||
|
instructions NOP and BRK:
|
||
|
|
||
|
Load and Store Instructions
|
||
|
Arithmetic Instructions
|
||
|
Increment and Decrement Instructions
|
||
|
Logical Instructions
|
||
|
Jump, Branch, Compare and Test Bits Instructions
|
||
|
Shift and Rotate Instructions
|
||
|
Transfer Instructions
|
||
|
Stack Instructions
|
||
|
Subroutine Instructions
|
||
|
Set/Reset Instructions
|
||
|
NOP/BRK Instructions
|
||
|
|
||
|
|
||
|
|
||
|
Load and Store Instructions
|
||
|
===========================
|
||
|
|
||
|
LDA - LoaD the Accumulator
|
||
|
LDX - LoaD the X register
|
||
|
LDY - LoaD the Y register
|
||
|
|
||
|
STA - STore the Accumulator
|
||
|
STX - STore the X register
|
||
|
STY - STore the Y register
|
||
|
|
||
|
Microprocessors spend much of their time moving stuff around in
|
||
|
memory. Data from one location is loaded into a register and stored
|
||
|
in another location, often with something added or subtracted in the
|
||
|
process. Memory can be loaded directly into the A, X, and Y registers
|
||
|
but as usual, the accumulator has more addressing modes available.
|
||
|
|
||
|
If the high bit (left most, bit 7) is set when loaded the N flag on
|
||
|
the processor status register is set. If the loaded value is zero the
|
||
|
Z flag is set.
|
||
|
|
||
|
|
||
|
Arithmetic Instructions
|
||
|
=======================
|
||
|
|
||
|
ADC - ADd to accumulator with Carry
|
||
|
SBC - SuBtract from accumulator with Carry
|
||
|
|
||
|
The 6502 has two arithmetic modes, binary and decimal. Both addition
|
||
|
and subtraction implement the carry flag to track carries and borrows
|
||
|
thereby making multibyte arithmetic simple. Note that in the case of
|
||
|
subtraction it is necessary to SET the carry flag as it is the opposite
|
||
|
of the carry that is subtracted.
|
||
|
|
||
|
Addition should follow this form:
|
||
|
|
||
|
CLC
|
||
|
ADC ...
|
||
|
.
|
||
|
.
|
||
|
ADC ...
|
||
|
.
|
||
|
.
|
||
|
.
|
||
|
|
||
|
Clear the carry flag, and perform all the additions. The carry
|
||
|
between additions will be handled in the carry flag. Add from low
|
||
|
byte to high byte. Symbolically, the net effect of an ADC instruction is:
|
||
|
|
||
|
A + M + C --> A
|
||
|
|
||
|
|
||
|
Subtraction follows the same format:
|
||
|
|
||
|
SEC
|
||
|
SBC ...
|
||
|
.
|
||
|
.
|
||
|
SBC ...
|
||
|
.
|
||
|
.
|
||
|
.
|
||
|
|
||
|
In this case set the carry flag first and then do the subtractions.
|
||
|
Symbolically,
|
||
|
|
||
|
A - M - ~C --> A , where ~C is the opposite of C
|
||
|
|
||
|
|
||
|
Ex.1
|
||
|
----
|
||
|
A 16-bit addition routine. $20,$21 + $22,$23 = $24,$25
|
||
|
|
||
|
CLC clear the carry
|
||
|
LDA $20 get the low byte of the first number
|
||
|
ADC $22 add to it the low byte of the second
|
||
|
STA $24 store in the low byte of the result
|
||
|
LDA $21 get the high byte of the first number
|
||
|
ADC $23 add to it the high byte of the second, plus carry
|
||
|
STA $25 store in high byte of the result
|
||
|
|
||
|
... on exit the carry will be set if the result could not be
|
||
|
contained in 16-bit number.
|
||
|
|
||
|
Ex.2
|
||
|
----
|
||
|
A 16-bit subtraction routine. $20,$21 - $22,$23 = $24,$25
|
||
|
|
||
|
SEC clear the carry
|
||
|
LDA $20 get the low byte of the first number
|
||
|
SBC $22 add to it the low byte of the second
|
||
|
STA $24 store in the low byte of the result
|
||
|
LDA $21 get the high byte of the first number
|
||
|
SBC $23 add to it the high byte of the second, plus carry
|
||
|
STA $25 store in high byte of the result
|
||
|
|
||
|
... on exit the carry will be set if the result produced a
|
||
|
borrow
|
||
|
|
||
|
Aside from the carry flag, arithmetic instructions also affect the N,
|
||
|
Z, and V flags as follows:
|
||
|
|
||
|
Z = 1 if result was zero, 0 otherwise
|
||
|
N = 1 if bit 7 of the result is 1, 0 otherwise
|
||
|
V = 1 if bit 7 of the accumulator was changed, a sign change
|
||
|
|
||
|
|
||
|
|
||
|
Increment and Decrement Instructions
|
||
|
====================================
|
||
|
|
||
|
INC - INCrement memory by one
|
||
|
INX - INcrement X by one
|
||
|
INY - INcrement Y by one
|
||
|
|
||
|
DEC - DECrement memory by one
|
||
|
DEX - DEcrement X by one
|
||
|
DEY - DEcrement Y by one
|
||
|
|
||
|
The 6502 has instructions for incrementing/decrementing the index
|
||
|
registers and memory. Note that it does not have instructions for
|
||
|
incrementing/decrementing the accumulator. This oversight was
|
||
|
rectified in the 65C02 which added INA and DEA instructions. The
|
||
|
index register instructions are implied mode for obvious reasons while
|
||
|
the INC and DEC instructions use a number of addressing modes.
|
||
|
|
||
|
All inc/dec instructions have alter the processor status flags in the
|
||
|
following way:
|
||
|
|
||
|
Z = 1 if the result is zero, 0 otherwise
|
||
|
N = 1 if bit 7 is 1, 0 otherwise
|
||
|
|
||
|
|
||
|
|
||
|
Logical Instructions
|
||
|
====================
|
||
|
|
||
|
AND - AND memory with accumulator
|
||
|
ORA - OR memory with Accumulator
|
||
|
EOR - Exclusive-OR memory with Accumulator
|
||
|
|
||
|
These instructions perform a bitwise binary operation according to the
|
||
|
tables given above. They set the Z flag if the net result is zero and
|
||
|
set the N flag if bit 7 of the result is set.
|
||
|
|
||
|
|
||
|
|
||
|
Jump, Branch, Compare, and Test Bits
|
||
|
====================================
|
||
|
|
||
|
JMP - JuMP to another location (GOTO)
|
||
|
|
||
|
BCC - Branch on Carry Clear, C = 0
|
||
|
BCS - Branch on Carry Set, C = 1
|
||
|
BEQ - Branch on EQual to zero, Z = 1
|
||
|
BNE - Branch on Not Equal to zero, Z = 0
|
||
|
BMI - Branch on MInus, N = 1
|
||
|
BPL - Branch on PLus, N = 0
|
||
|
BVS - Branch on oVerflow Set, V = 1
|
||
|
BVC - Branch on oVerflow Clear, V = 0
|
||
|
|
||
|
CMP - CoMPare memory and accumulator
|
||
|
CPX - ComPare memory and X
|
||
|
CPY - ComPare memory and Y
|
||
|
|
||
|
BIT - test BITs
|
||
|
|
||
|
This large group includes all instructions that alter the flow of the
|
||
|
program or perform a comparison of values or bits.
|
||
|
|
||
|
JMP simply sets the program counter (PC) to the address given.
|
||
|
Execution proceeds from the new address. The branch instructions are
|
||
|
relative jumps. They cause a branch to a new address that is either
|
||
|
127 bytes beyond the current PC or 128 bytes before the current PC.
|
||
|
Code that only uses branch instructions is relocatable and can be run
|
||
|
anywhere in memory.
|
||
|
|
||
|
The three compare instructions are used to set processor status bits.
|
||
|
After the comparison one frequently branches to a new place in the
|
||
|
program based on the settings of the status register. The
|
||
|
relationship between the compared values and the status bits is,
|
||
|
|
||
|
|
||
|
+-------------------------+---------------------+
|
||
|
| | N Z C |
|
||
|
+-------------------------+---------------------+
|
||
|
| A, X, or Y < Memory | 1 0 0 |
|
||
|
| A, X, or Y = Memory | 0 1 1 |
|
||
|
| A, X, or Y > Memory | 0 0 1 |
|
||
|
+-----------------------------------------------+
|
||
|
|
||
|
|
||
|
The BIT instruction tests bits in memory with the accumulator but
|
||
|
changes neither. Only processor status flags are set. The contents
|
||
|
of the specified memory location are logically ANDed with the
|
||
|
accumulator, then the status bits are set such that,
|
||
|
|
||
|
* N receives the initial, un-ANDed value of memory bit 7.
|
||
|
* V receives the initial, un-ANDed value of memory bit 6.
|
||
|
* Z is set if the result of the AND is zero, otherwise reset.
|
||
|
|
||
|
So, if $23 contained $7F and the accumulator contained $80 a BIT $23
|
||
|
instruction would result in the V and Z flags being set and N reset since
|
||
|
bit 7 of $7F is 0, bit 6 of $7F is 1, and $7F AND $80 = 0.
|
||
|
|
||
|
|
||
|
|
||
|
Shift and Rotate Instructions
|
||
|
=============================
|
||
|
|
||
|
ASL - Accumulator Shift Left
|
||
|
LSR - Logical Shift Right
|
||
|
ROL - ROtate Left
|
||
|
ROR - ROtate Right
|
||
|
|
||
|
Use these instructions to move things around in the accumulator or
|
||
|
memory. The net effects are (where C is the carry flag):
|
||
|
|
||
|
|
||
|
+-+-+-+-+-+-+-+-+
|
||
|
C <- |7|6|5|4|3|2|1|0| <- 0 ASL
|
||
|
+-+-+-+-+-+-+-+-+
|
||
|
|
||
|
+-+-+-+-+-+-+-+-+
|
||
|
0 -> |7|6|5|4|3|2|1|0| -> C LSR
|
||
|
+-+-+-+-+-+-+-+-+
|
||
|
|
||
|
+-+-+-+-+-+-+-+-+
|
||
|
C <- |7|6|5|4|3|2|1|0| <- C ROL
|
||
|
+-+-+-+-+-+-+-+-+
|
||
|
|
||
|
+-+-+-+-+-+-+-+-+
|
||
|
C -> |7|6|5|4|3|2|1|0| -> C ROR
|
||
|
+-+-+-+-+-+-+-+-+
|
||
|
|
||
|
Z is set if the result it zero. N is set if bit 7 is 1. It is
|
||
|
always reset on LSR. Remember that ASL A is equal to multiplying by
|
||
|
two and that LSR is equal to dividing by two.
|
||
|
|
||
|
|
||
|
|
||
|
Transfer Instructions
|
||
|
=====================
|
||
|
|
||
|
TAX - Transfer Accumulator to X
|
||
|
TAY - Transfer Accumulator to Y
|
||
|
TXA - Transfer X to accumulator
|
||
|
TYA - Transfer Y to Accumulator
|
||
|
|
||
|
Transfer instructions move values between the 6502 registers. The N
|
||
|
and Z flags are set if the value being moved warrants it, i.e.
|
||
|
|
||
|
LDA #$80
|
||
|
TAX
|
||
|
|
||
|
causes the N flag to be set since bit 7 of the value moved is 1, while
|
||
|
|
||
|
LDX #$00
|
||
|
TXA
|
||
|
|
||
|
causes the Z flag to be set since the value is zero.
|
||
|
|
||
|
|
||
|
|
||
|
Stack Instructions
|
||
|
==================
|
||
|
|
||
|
TSX - Transfer Stack pointer to X
|
||
|
TXS - Transfer X to Stack pointer
|
||
|
|
||
|
PHA - PusH Accumulator on stack
|
||
|
PHP - PusH Processor status on stack
|
||
|
PLA - PulL Accumulator from stack
|
||
|
PLP - PulL Processor status from stack
|
||
|
|
||
|
TSX and TXS make manipulating the stack possible. The push and pull
|
||
|
instructions are useful for saving register values and status flags.
|
||
|
Their operation is straightforward.
|
||
|
|
||
|
|
||
|
|
||
|
Subroutine Instructions
|
||
|
=======================
|
||
|
|
||
|
JSR - Jump to SubRoutine
|
||
|
RTS - ReTurn from Subroutine
|
||
|
RTI - ReTurn from Interrupt
|
||
|
|
||
|
Like JMP, JSR causes the program to start execution of the next
|
||
|
instruction at the given address. Unlike JMP, JSR pushes the address
|
||
|
of the next instruction after itself on the stack. When an RTS
|
||
|
instruction is executed the address pushed on the stack is pulled off
|
||
|
the stack and the program resumes at that address. For example,
|
||
|
|
||
|
LDA #$C1 ; load the character 'A'
|
||
|
JSR print ; print the character and it's hex code
|
||
|
LDA #$C2 ; load 'B'
|
||
|
JSR print ; and print it
|
||
|
.
|
||
|
.
|
||
|
.
|
||
|
print JSR $FDED ; print the letter
|
||
|
JSR $FDDA ; and its ASCII code
|
||
|
RTS ; return to the caller
|
||
|
|
||
|
RTI is analagous to RTS and should be used to end an interrupt routine.
|
||
|
|
||
|
|
||
|
|
||
|
Set and Reset (Clear) Instructions
|
||
|
==================================
|
||
|
|
||
|
CLC - CLear Carry flag
|
||
|
CLD - CLear Decimal mode
|
||
|
CLI - CLear Interrupt disable
|
||
|
CLV - CLear oVerflow flag
|
||
|
|
||
|
SEC - SEt Carry
|
||
|
SED - SEt Decimal mode
|
||
|
SEI - SEt Interrupt disable
|
||
|
|
||
|
These are one byte instructions to specify processor status flag
|
||
|
settings.
|
||
|
|
||
|
CLC and SEC are of particular use in addition and subtraction
|
||
|
respectively. Before any addition (ADC) use CLC to clear the carry
|
||
|
or the result may be one greater than you expect. For subtraction
|
||
|
(SBC) use SEC to ensure that the carry is set as its compliment is
|
||
|
subtracted from the answer. In multi-byte additions or subtractions
|
||
|
only clear or set the carry flag before the initial operation. For
|
||
|
example, to add one to a 16-bit number in $23 and $24 you would write:
|
||
|
|
||
|
LDA $23 ; get the low byte
|
||
|
CLC ; clear the carry
|
||
|
ADC #$02 ; add a constant 2, carry will be set if result > 255
|
||
|
STA $23 ; save the low byte
|
||
|
LDA $24 ; get the high byte
|
||
|
ADC #$00 ; add zero to add any carry that might have been set above
|
||
|
STA $24 ; save the high byte
|
||
|
RTS ; if carry set now the result was > 65535
|
||
|
|
||
|
Similarly for subtraction,
|
||
|
|
||
|
LDA $23 ; get the low byte
|
||
|
SEC ; set the carry
|
||
|
SBC #$02 ; subtract 2
|
||
|
STA $23 ; save the low byte
|
||
|
LDA $24 ; get the high byte
|
||
|
SBC #$00 ; subtract 0 and any borrow generated above
|
||
|
STA $24 ; save the high byte
|
||
|
RTS ; if the carry is not set the result was < 0
|
||
|
|
||
|
|
||
|
|
||
|
Other Instructions
|
||
|
==================
|
||
|
|
||
|
NOP - No OPeration (or is it NO oPeration ? :)
|
||
|
BRK - BReaK
|
||
|
|
||
|
NOP is just that, no operation. Useful for deleting old
|
||
|
instructions, reserving room for future instructions or for use in
|
||
|
careful timing loops as it uses 2 microprocessor cycles.
|
||
|
|
||
|
BRK causes a forced break to occur and the processor will immediately
|
||
|
start execution of the routine whose address is in $FFFE and $FFFF.
|
||
|
This address is often the start of a system monitor program.
|
||
|
|
||
|
|
||
|
|
||
|
Some simple programming examples
|
||
|
================================
|
||
|
|
||
|
A few simple programming examples are given here. They serve to
|
||
|
illustrate some techniques commonly used in assembly programming.
|
||
|
There are doubtless dozens more and I make no claim at being a
|
||
|
proficient assembly language programmer. For examples of addition
|
||
|
and subtraction see above on CLC and SEC.
|
||
|
|
||
|
|
||
|
A count down loop
|
||
|
-----------------
|
||
|
|
||
|
;
|
||
|
; An 8-bit count down loop
|
||
|
;
|
||
|
|
||
|
start LDX #$FF ; load X with $FF = 255
|
||
|
loop DEX ; X = X - 1
|
||
|
BNE loop ; if X not zero then goto loop
|
||
|
RTS ; return
|
||
|
|
||
|
How does the BNE instruction know that X is zero? It
|
||
|
doesn't, all it knows is that the Z flag is set or reset.
|
||
|
The DEX instruction will set the Z flag when X is zero.
|
||
|
|
||
|
|
||
|
;
|
||
|
; A 16-bit count down loop
|
||
|
;
|
||
|
|
||
|
start LDY #$FF ; load Y with $FF
|
||
|
loop1 LDX #$FF ; load X with $FF
|
||
|
loop2 DEX ; X = X - 1
|
||
|
BNE loop2 ; if X not zero goto loop2
|
||
|
DEY ; Y = Y - 1
|
||
|
BNE loop1 ; if Y not zero goto loop1
|
||
|
RTS ; return
|
||
|
|
||
|
There are two loops here, X will be set to 255 and count to
|
||
|
zero for each time Y is decremented. The net result is to
|
||
|
count the 16-bit number Y (high) and X (low) down from $FFFF
|
||
|
= 65535 to zero.
|
||
|
|
||
|
|
||
|
Other examples
|
||
|
--------------
|
||
|
|
||
|
** Note: All of the following examples are lifted nearly verbatim from
|
||
|
the book "6502 Software Design", whose reference is above.
|
||
|
|
||
|
|
||
|
; Example 4-2. Deleting an entry from an unordered list
|
||
|
;
|
||
|
; Delete the contents of $2F from a list whose starting
|
||
|
; address is in $30 and $31. The first byte of the list
|
||
|
; is its length.
|
||
|
;
|
||
|
|
||
|
deluel LDY #$00 ; fetch element count
|
||
|
LDA ($30),Y
|
||
|
TAX ; transfer length to X
|
||
|
LDA $2F ; item to delete
|
||
|
nextel INY ; index to next element
|
||
|
CMP ($30),Y ; do entry and element match?
|
||
|
BEQ delete ; yes. delete element
|
||
|
DEX ; no. decrement element count
|
||
|
BNE nextel ; any more elements to compare?
|
||
|
RTS ; no. element not in list. done
|
||
|
|
||
|
; delete an element by moving the ones below it up one location
|
||
|
|
||
|
delete DEX ; decrement element count
|
||
|
BEQ deccnt ; end of list?
|
||
|
INY ; no. move next element up
|
||
|
LDA ($30),Y
|
||
|
DEY
|
||
|
STA ($30),Y
|
||
|
INY
|
||
|
JMP delete
|
||
|
deccnt LDA ($30,X) ; update element count of list
|
||
|
SBC #$01
|
||
|
STA ($30,X)
|
||
|
RTS
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
; Example 5-6. 16-bit by 16-bit unsigned multiply
|
||
|
;
|
||
|
; Multiply $22 (low) and $23 (high) by $20 (low) and
|
||
|
; $21 (high) producing a 32-bit result in $24 (low) to $27 (high)
|
||
|
;
|
||
|
|
||
|
mlt16 LDA #$00 ; clear p2 and p3 of product
|
||
|
STA $26
|
||
|
STA $27
|
||
|
LDX #$16 ; multiplier bit count = 16
|
||
|
nxtbt LSR $21 ; shift two-byte multiplier right
|
||
|
ROR $20
|
||
|
BCC align ; multiplier = 1?
|
||
|
LDA $26 ; yes. fetch p2
|
||
|
CLC
|
||
|
ADC $22 ; and add m0 to it
|
||
|
STA $26 ; store new p2
|
||
|
LDA $27 ; fetch p3
|
||
|
ADC $23 ; and add m1 to it
|
||
|
align ROR A ; rotate four-byte product right
|
||
|
STA $27 ; store new p3
|
||
|
ROR $26
|
||
|
ROR $25
|
||
|
ROR $24
|
||
|
DEX ; decrement bit count
|
||
|
BNE nxtbt ; loop until 16 bits are done
|
||
|
RTS
|
||
|
|
||
|
|
||
|
|
||
|
; Example 5-14. Simple 16-bit square root.
|
||
|
;
|
||
|
; Returns the 8-bit square root in $20 of the
|
||
|
; 16-bit number in $20 (low) and $21 (high). The
|
||
|
; remainder is in location $21.
|
||
|
|
||
|
sqrt16 LDY #$01 ; lsby of first odd number = 1
|
||
|
STY $22
|
||
|
DEY
|
||
|
STY $23 ; msby of first odd number (sqrt = 0)
|
||
|
again SEC
|
||
|
LDA $20 ; save remainder in X register
|
||
|
TAX ; subtract odd lo from integer lo
|
||
|
SBC $22
|
||
|
STA $20
|
||
|
LDA $21 ; subtract odd hi from integer hi
|
||
|
SBC $23
|
||
|
STA $21 ; is subtract result negative?
|
||
|
BCC nomore ; no. increment square root
|
||
|
INY
|
||
|
LDA $22 ; calculate next odd number
|
||
|
ADC #$01
|
||
|
STA $22
|
||
|
BCC again
|
||
|
INC $23
|
||
|
JMP again
|
||
|
nomore STY $20 ; all done, store square root
|
||
|
STX $21 ; and remainder
|
||
|
RTS
|
||
|
|
||
|
|
||
|
This is based on the observation that the square root of an
|
||
|
integer is equal to the number of times an increasing odd
|
||
|
number can be subtracted from the original number and remain
|
||
|
positive. For example,
|
||
|
|
||
|
25
|
||
|
- 1 1
|
||
|
--
|
||
|
24
|
||
|
- 3 2
|
||
|
--
|
||
|
21
|
||
|
- 5 3
|
||
|
--
|
||
|
16
|
||
|
- 7 4
|
||
|
--
|
||
|
9
|
||
|
- 9 5 = square root of 25
|
||
|
--
|
||
|
0
|
||
|
|
||
|
If you are truly interested in learning more, go to your public library
|
||
|
and seek out an Apple machine language programming book. If your public
|
||
|
library is like mine, there will still be plenty of early 80s computer
|
||
|
books on the shelves. :)
|
||
|
|
||
|
------------------------------------------------------------------------
|
||
|
Back to the Incredible 6502
|
||
|
|