411 lines
17 KiB
Plaintext
411 lines
17 KiB
Plaintext
====================================================================
|
||
DR 6502 AER 201S Engineering Design 6502 Execution Simulator
|
||
====================================================================
|
||
|
||
Supplementary Notes By: M.J.Malone
|
||
|
||
Starting to Program in 6502 Assembly Code
|
||
=========================================
|
||
|
||
Quotes of Murphy's Kin:
|
||
|
||
1) It is impossible to make anything fool-proof because fools are
|
||
so ingenious.
|
||
|
||
2) If you explain something so thoroughly and clearly so that
|
||
everyone will understand, someone will not understand.
|
||
|
||
|
||
Introduction to the 6502
|
||
------------------------
|
||
|
||
Accumulator and Assembler Basics
|
||
|
||
The 6502 has several 'registers'. Registers are special memory
|
||
locations that are internal to the processor and are directly
|
||
involved in processor instruction codes. The accumulator (.ACC) is
|
||
one such register. The code fragment that would transfer data from
|
||
one memory location (A_loc) to another (B_loc) would be coded as:
|
||
(Note that the numbers $0400 and $0800 are hexidecimal numbers which
|
||
do have decimal equivalents. The conversion between hex, decimal
|
||
and binary will be explained later.)
|
||
|
||
;
|
||
A_loc = $0400
|
||
B_loc = $0800
|
||
;
|
||
LDA A_loc
|
||
STA B_loc
|
||
;
|
||
|
||
Where LDA is the mnemonic for LoaD Accumulator and STA is the
|
||
mnemonic for STore Accumulator. In this example the two
|
||
assignments 'A_loc=' and 'B_loc=' give values to the labels A_loc
|
||
and B_loc. Note that A_loc and B_loc are NOT variables but labels
|
||
or constants used to replace numerical values and make the code more
|
||
readable. The locations described by A_loc and B_loc will contain
|
||
data important to the running of the program, the variables. Now
|
||
A_loc and B_loc are not very descriptive but 'Abs_Value' and
|
||
'Buffer_ptr' are also legal labels. The assembler sees the above
|
||
code fragment as:
|
||
|
||
;
|
||
LDA $0400
|
||
STA $0800
|
||
;
|
||
|
||
with the labels substituted into the code. This fragment loads the
|
||
value from address $0400 into the accumulator and then stores the
|
||
value into memory address $0800.
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
page 2
|
||
|
||
You may think of labels 'Buffer_ptr' as a variable as in a high
|
||
level language when you go 'LDA Buffer_ptr' to get the value of the
|
||
variable into the accumulator. Remember that the assembler sees
|
||
Buffer_ptr as an address of a location in memory, it is the
|
||
programmer who decides that that location has a special meaning or
|
||
purpose and gives it a meaningful name.
|
||
|
||
|
||
The .X and .Y Registers and Indexing
|
||
|
||
The .X and .Y registers are similar to the .ACC in most
|
||
respects, data can be loaded into them and stored into memory from
|
||
them. The above example could as easily be programmed with LDX and
|
||
STX as it was with LDA and STA as follows:
|
||
|
||
;
|
||
A_loc = $0400
|
||
B_loc = $0800
|
||
;
|
||
LDX A_loc
|
||
STX B_loc
|
||
;
|
||
|
||
The same code could also be written for the .Y but there is a
|
||
special purpose reserved for the .X and .Y registers. In the
|
||
previous section we saw that labels were used to name important
|
||
memory locations and simulate the function of variables for the
|
||
programmer. The assignment 'b=a' in a higher level language may be
|
||
coded using the example above. If the programmer wanted to use an
|
||
array and say assign 'b[3]=a' it may be done as follows:
|
||
|
||
;
|
||
A_loc = $0400
|
||
B_array_start = $0800
|
||
;
|
||
LDA A_loc
|
||
LDX #3
|
||
STA B_array_start,X
|
||
;
|
||
|
||
As before, the value of memory location 'A_loc' is loaded into
|
||
memory. The value of the array index '3' is loaded into the X
|
||
register. The statement 'STA B_array_start,X' takes the value in
|
||
the memory and stores it into the location (B_array_start+.X). Here
|
||
B_array_start is the first address for the array and .X acts as the
|
||
offset INDEX into the memory space that follows. This is called
|
||
simple indexing and can be used to store and recall data that is
|
||
organized into tables.
|
||
|
||
|
||
The 6502 as an 8 bit Processor
|
||
|
||
For programmers accustomed to high level languages often the
|
||
assembly language of processors seems very limiting. So far the
|
||
implementation of higher level language assignment statements has
|
||
seemed to be as simple as a few substitutions, a few LDA and STA to
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
page 3
|
||
|
||
get the job done. Unfortunately this is about the easiest high
|
||
level language concept to transfer to assembly and then only in very
|
||
restricted cases.
|
||
The 6502 is an 8 bit processor and all memory fetches (LDA etc)
|
||
read values from 8 bit memory locations. All numbers stored in the
|
||
accumulator are in the range of 0-255 or $00-$FF. The .X and .Y
|
||
registers are also 8 bit as is the .SP the stack pointer for the
|
||
6502. This limits the stack to 256 memory locations (located
|
||
between addresses $0100-$01FF). The program counter is a 16 bit
|
||
number which varies between $0000 and $FFFF. The address space is
|
||
defined as the range of memory locations that can be pointed to by
|
||
the program counter. In the case of the 6502, 16 bits, $0000-$FFFF,
|
||
0-65535 represents an address space of 64K, where 1K is defined as
|
||
1024 for binary applications.
|
||
|
||
Number Conversions
|
||
The conversions between hex and decimal representation can be
|
||
done however students should be aware that since the 6502 is an 8
|
||
bit machine, everything is much more convenient in hex. For
|
||
instance the address $8000 is immediately recognizable as the first
|
||
address in the upper half of memory (because it begins $80), at the
|
||
beginning of a memory page (because it ends in 00). The equivalent
|
||
number in decimal 32768, unless memorized for what it is, cannot be
|
||
recognized as easily. Similarly it is necessary when dealing with
|
||
I/O ports to control individual bits of a memory location. In this
|
||
case the binary representation of %1010000 is immediately
|
||
recognizable as bits 7 and 5 of the byte set whereas the meaning of
|
||
the equivalent decimal number 160 is not nearly so clear.
|
||
It is advisable to use hexidecimal numbers for addresses,
|
||
binary numbers where bit on/off states are important. Decimal
|
||
numbers should be used only when 'magic' numbers are such as 26
|
||
letters in the alphabet, 24 hours in a day, 30 days in a month etc
|
||
that we are accustomed to seeing as decimal are needed. Note that
|
||
numbers 9 or smaller are the same in hex as decimal so there is no
|
||
problem for small constants.
|
||
The conversion between hex, binary and decimal will most likely
|
||
be done on students hand calculators however recalling what you
|
||
learned in grade 3: There are 10 symbols for digits in the decimal
|
||
number system 0-9. All numbers are stated with these 10 symbols
|
||
(note the number 10 is expressed in decimal). Numbers larger than 9
|
||
are stated using ten's and unit's digits:
|
||
|
||
98 = 9*10 + 8
|
||
|
||
or more generally:
|
||
|
||
34256 = 3*10^4 + 4*10^3 + 2*10^2 + 5*10^1 + 6*10^0
|
||
|
||
Hexidecimal numbers are represented with 16 symbols ($10 = 16 in
|
||
hex), the symbols 0-9 and the letters A-F. As units digits, 0-9
|
||
correspond to the decimal numbers 0-9, ie: 5 = $5 etc. As units
|
||
digits, A-F correspond to the decimal numbers 10-15. A hexidecimal
|
||
number can be expressed:
|
||
|
||
$7A3F = $7*$10^3 + $A*$10^2 + $3*$10^1 + $F*$10^0
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
page 4
|
||
|
||
|
||
So far this is parallel to the representation of a decimal number as
|
||
above. To perform a conversion you would simply convert the
|
||
hexidecimal numbers in the expansion to decimal.
|
||
|
||
$7A3F = 7*16^3 + 10*16^2 + 3*16^1 + 15*16^0 = 31295 decimal
|
||
|
||
A similar operation can be done in binary but once the meaning of
|
||
units, ten's and hundreds digits is understood then the expansions
|
||
of binary numbers are as easily calculated as for hexidecimal
|
||
numbers.
|
||
Often conversions between hexidecimal and binary or the reverse
|
||
are necessary. Similar expansions as above could be performed but
|
||
there is a short cut. Noting that 2^4 = 16 and 2 is the base of the
|
||
binary system and 16 is base of the hexidecimal system, groups of
|
||
four binary digits must somehow correspond to hexidecimal digits.
|
||
Observing that:
|
||
|
||
%0000 = $0 %1000 = $8
|
||
%0001 = $1 %1001 = $9
|
||
%0010 = $2 %1010 = $A
|
||
%0011 = $3 %1011 = $B
|
||
%0100 = $4 %1100 = $C
|
||
%0101 = $5 %1101 = $D
|
||
%0110 = $6 %1110 = $E
|
||
%0111 = $7 %1111 = $F
|
||
|
||
The conversion of multidigit hexidecimal numbers becomes
|
||
straight forward.
|
||
|
||
$7A3F = $7,A,3,F = %0111,1010,0011,1111 = %0111101000111111
|
||
|
||
This is very useful when hardware tests have to be done. If
|
||
the program counter of the 6502 were pointing at address $7A3F then
|
||
the binary representation would be useful for understanding the
|
||
voltage values on the address pins of the 6502 chip. In digital
|
||
circuits, 5 volts corresponds to logic 1 or binary 1; 0 volts
|
||
corresponds to logic 0 or binary 0. The binary representation of
|
||
the address %0111101000111111 is also the logic levels for the
|
||
address lines Adr15-Adr0 and hence the voltages on the address lines
|
||
would read (counting from Adr15 to 0): 0V, 5V, 5V, 5V, 5V, 0V, 5V,
|
||
0V, 0V, 0V, 5V, 5V, 5V, 5V, 5V and 5V. Summary of 6502 Basic
|
||
Structure
|
||
|
||
By now you should be realizing that operation of digital
|
||
computers are really not so mysterious at all. True the internal
|
||
workings of a 6502 are very complex, it is not hard to imagine it
|
||
being made up of a great number of smaller blocks that interpret
|
||
voltages as logic levels and perform logical operations. The
|
||
acculumator is in fact eight circuits that can each hold a voltage
|
||
of 0 or 5 volts, organized into a unit that is routed out of the
|
||
processor to memory by the STA instruction for instance. Since the
|
||
actual operations, when reduced to voltages and switching
|
||
transistors are very simple, they are very fast. Even the 6502, a
|
||
relatively slow processor can perform a STA operation in as little
|
||
as 3 microseconds or perform 333,333 such STA's in one second of
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
page 5
|
||
|
||
operation in the slowest 1 MHz implementation.
|
||
We have been introduced to the accumulator and the .X and .Y
|
||
index registers. As mentioned before the system 8 bit stack
|
||
pointer .SP points into an area of memory from $0100-$01FF. For now
|
||
suffice it to say that the stack is very important to the operation
|
||
of the processor and the actual instructions that involve the stack
|
||
will be discussed later. The program counter is a 16 bit register
|
||
that points to the instruction in memory that is currently being
|
||
executed. The processor status register which has not been
|
||
discussed until now, is a group of flags that represent the current
|
||
state of the processor and the results of the last calculation
|
||
performed.
|
||
|
||
|
||
Classes of 6502 Instructions
|
||
----------------------------
|
||
|
||
The reader will be introduced to several classes of 6502
|
||
instructions. Some instructions require arguments and some do not
|
||
and this is indicated where appropriate.
|
||
|
||
Data Transfer Instructions
|
||
There are several instructions in the family of data transfer
|
||
instructions but basically they fall into two categories, those
|
||
which move data between memory and registers and those that move
|
||
data between registers.
|
||
|
||
LDA, LDX, LDY arg Load 'arg' into Accumulator, X or Y registers
|
||
STA, STX, STY arg Store the Accumulator, X or Y registers to memory 'arg'
|
||
STZ arg *Store the value zero into a memory location 'arg'
|
||
|
||
TAX, TAY Transfer .A to .X, .A to .Y
|
||
TXA, TYA Transfer .X to .A, .Y to .A
|
||
TXS, TSX Transfer .X to .SP, .SP to .X
|
||
|
||
*65C02 Only
|
||
|
||
These instructions are very straight forward, they move an 8
|
||
bit value ($00-$FF) from the one place to another. After the
|
||
instruction, the data is in two places, where it was and where it
|
||
was moved to.
|
||
|
||
|
||
Go to Instructions
|
||
|
||
The go to instructions are as follows:
|
||
|
||
JMP dest Jump, Set the Program Counter = 'dest'
|
||
|
||
JSR dest Jump to a Subroutine, saving the return address
|
||
RTS Return from subroutine
|
||
|
||
The JMP statement is a pure 'go to' which simply sets the
|
||
program counter to a new address and continues the execution of the
|
||
code at that point. The JSR statement first takes that current
|
||
program counter position and pushes it into the stack and then jumps
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
page 6
|
||
|
||
to the new address. When the RTS instruction is encountered at the
|
||
end of the subroutine, the previous program location is pulled from
|
||
the stack and the processor continues after the subroutine call.
|
||
|
||
|
||
Compare Instructions
|
||
|
||
So far we have examined ways of moving data and unconditionally
|
||
changing the path of the program. We will now look at ways to
|
||
conditionally change the path of the program. In machine language
|
||
this is divided into two operations, a comparison and a branch.
|
||
There are three compare statements:
|
||
|
||
CMP arg Compare the .A
|
||
CPX arg .X
|
||
CPY arg .Y to 'arg'
|
||
|
||
The results of these comparisons are recorded in the system
|
||
flags N-negative, Z-zero and C-carry for later use in branch
|
||
instructions.
|
||
|
||
Branch Instructions
|
||
Branch instructions test the state of one of the flags and
|
||
either branch or not. Branches are relative jumps up to 128 bytes
|
||
forward or back in the code determined by the offset argument. The
|
||
branch instructions are:
|
||
|
||
BNE offset Branch on result not equal , result not zero: Z=0
|
||
BEQ offset Branch on result equal , result zero : Z=1
|
||
BMI offset Branch on result greater , result <0 : N=1
|
||
BPL offset Branch on result less or equal, result =>0 : N=0
|
||
BCC offset Branch on carry clear : C=0
|
||
BCS offset Branch on carry set : C=1
|
||
BVC offset Branch on overflow clear : V=0
|
||
BVS offset Branch on overflow set : V=1
|
||
BRA offset *Branch always
|
||
|
||
*65C02 only
|
||
|
||
|
||
Arithmetic and Logical Instructions
|
||
|
||
We have examined data moving and comparing instructions,
|
||
conditional branches and unconditional changes to the program flow.
|
||
The last class of instructions we will examine are the arithmetic
|
||
and logical instructions where actual computations occur. All of
|
||
these instructions involve the accumulator as one of the arguments.
|
||
|
||
ADC arg Add with carry .A + 'arg' + C ==> .A (C,V,N,Z)
|
||
SBC arg Subtract with borrow .A - 'arg' - !C ==> .A (C,V,N,Z)
|
||
AND arg Logical bitwise And .A and 'arg' ==> .A (N,Z)
|
||
ORA arg Logical bitwise Or .A or 'arg' ==> .A (N,Z)
|
||
EOR arg Logical bitwise XOr .A xor 'arg' ==> .A (N,Z)
|
||
|
||
The ADC and SBC use the carry to allow multibyte additions to
|
||
be done using the C flag to carry to or borrow from the next higher
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
page 7
|
||
|
||
byte of the operation. The V flag is the overflow flag. It is set
|
||
whenever a computation exceeds $FF or goes under $00. Note that the
|
||
overflow flag can also be set using the SO set overflow pin on the
|
||
6502 allowing an additional input line to the processor.
|
||
|
||
Instruction Summary
|
||
-------------------
|
||
There are other instructions for the 6502 but they are less
|
||
commonly used then those introduced above. It would be most useful
|
||
now to attempt to use the instructions learned in a few code
|
||
fragments to see how instructions interrelate.
|
||
|