on Wednesday 27 October 2010
in Commodore 64 > Programming

Nice introduction to 6502 programming

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
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

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

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

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.