|
 |
 |
4
Addressing
The 6502 processor
is an electronic brain. It performs a variety of manipulations with
numbers to allow us to write words, draw pictures, control outside
machines such as tape recorders, calculate, and do many other
things. Its manipulations were designed to be logical and fast. The
computer has been designed to permit everything to be accomplished
accurately and efficiently. If you could peer
down into the CPU (Central Processing Unit), the heart of the
computer, you would see numbers being delivered and received from
memory locations all over the computer. Sometimes the numbers arrive
and are sent out, unchanged, to some other address. Other times they
are compared, added, or otherwise modified, before being sent back
to RAM or to a peripheral. Writing an ML
program can be compared to planning the activities of this message
center. It can be illustrated by thinking of computer memory as a
city of bytes and the CPU as the main post office. (See Figure 4-1.)
The CPU does its job using several tools: three registers, a program
counter, a stack pointer, and seven little one-bit flags contained
in a byte called the Status Register. We will only concern ourselves
with the "C" (carry) flag and the "Z" (it equals zero) flags. The
rest of them are far less frequently needed for ML programming so
we'll only describe them briefly. (See Figure 4-1.)
Most monitors, after you BRK (like BASIC's STOP) out of a
program, will display the present status of these tools. It looks
something like this:
Program 4-1. Current Status Of
The Registers.
PC IRQ SR AC XR YR SP 0005 E455 30 00 5E 04
F8
The PC is the Program
Counter and it is two bytes long so it can refer to a location
anywhere in memory. The IRQ is also two bytes and points to a ROM ML
routine which handles interrupts, special-priority actions. A
beginning ML programmer will not be working with interrupts and need
not worry about the IRQ. You can also more or less let the computer
handle the SP on the end. It's the stack pointer. The SP keeps track
of numbers, usually return-from-subroutine addresses which are kept
together in a list called the stack. The
computer will automatically handle the stack pointer for us. It will
also deal with IRQ and the program counter. For example, each ML
instruction we give it could be one, two, or three bytes long. TYA
has no argument and is the instruction to transfer a number from the
Y register to the accumulator. Since it has no argument, the PC can
locate the next instruction to be carried out by raising itself by
one. If the PC held $4000, it would hold $4001 after execution of a
TYA. LDA #$01 is a two-byte instruction. It takes up two bytes in
memory so the next instruction to be executed after LDA #$01 will be
two bytes beyond it. In this case, the PC will raise itself from
$4000 to $4002. But we can just let it work merrily away without
worrying about it.
|
 |
 |
The Accumulator: The
Busiest Register The SR, AC, XR, and YR, however, are
our business. They are all eight bits (one byte) in size. They are
not located in memory proper. You can't PEEK them since they have no
address like the rest of memory. They are zones of the CPU. The AC,
or A register, but most often called the accumulator, is the busiest
place in the computer. The great bulk of the mail comes to rest
here, if only briefly, before being sent to another
destination. Any logical transformations
(EOR,AND) or arithmetic operations leave their results in the
accumulator. Most of the bytes streaming through the computer come
through the accumulator. You can compare one byte against another
using the accumulator. And nearly everything that happens which
involves the accumulator will have an effect on the status register
(SR, the flags). The X and Y registers are
similar to each other in that one of their main purposes is to
assist the accumulator. They are used as addressing indexes. There
are addressing modes that we'll get to in a minute which add an
index value to another number. For example, LDA $4000,X will load
into A the number found in address $4005, if the X register is
currently holding a five. The address is the number plus the index
value. If X has a six, then we load from $4006. Why not just LDA
$4006? It is far easier to raise or lower an index inside a loop
structure than it would be to write in each specific address
literally. A second major use of X and Y is
in counting and looping. We'll go into this more in the chapter on
the instruction set. We'll also have some
things to learn later about the SR, the Status Register which holds
some flags showing current conditions. The SR can tell a program or
the CPU if there has been a zero, a carry, or a negative number as
the result of some operation, among other things. Knowing about
carry and zero flags is especially significant in ML.
For now, the task at hand is to explore the various
"classes" of mail delivery, the 6502 addressing modes.
Aside from comparing things and so forth, the computer
must have a logical way to pick up and send information. Rather like
a postal service in a dream - everything should be picked up and
delivered rapidly, and nothing should be lost, damaged, or delivered
to the wrong address. The 6502 accomplishes
its important function of getting and sending bytes (GET and PRINT
would be examples of this same thing in BASIC) by using several
"addressing modes." There are 13 different ways that a byte might be
"mailed" either to or from the central processor.
When programming, in addition to picking an instruction (of
the 56 available to you) to accomplish the job you are working on,
you must also make one other decision. You must decide how you want
to address the instruction - how, in other words, you want the mail
sent or delivered. There is some room for maneuvering. You will
probably not care if you accidentally choose a slower delivery
method than you could have. Nevertheless, it is necessary to know
what choices you have: most addressing modes are designed to aid a
common programming activity.
Absolute And
Zero Let's picture a postman's dream city, a city so
well planned from a postal-delivery point of view that no byte is
ever lost, damaged, or sent to the wrong address. It's the City of
Bytes we first toured in Chapter 2. It has 65536 houses all lined up
on one side of a street (a long street). Each house is clearly
labeled with its number, starting with house zero and ending with
house number 65535. When you want to get a byte from, or send a byte
to, a house (each house holds one byte) - you must "address" the
package. (See Figure 4-2.)
|
 |
 |
Here's an example of one mode of addressing.
It's quite popular and could be thought of as "First Class." Called
absolute addressing, it can send a number to, or receive one from,
any house in the city. It's what we normally think of first when the
idea of "addressing" something comes up. You just put the number on
the package and send it off. No indexing or special instructions. If
it says 2500, then it means house 2500.
1000 STA $2500 or 1000 LDA
$2500
These two, STore A and
LoaD A, STA and LDA, are the instructions which get a byte from, or
send it to, the accumulator. The address, though, is found in the
numbers following the instruction. The items following an
instruction are called the instruction's argument. You could have
written the address several ways. Writing it as $2500 tells your
assembler to get it from, or send it directly to, hex $2500. This
kind of addressing uses just a simple $ and a four-digit number. You
can send the byte sitting in the accumulator to anywhere in RAM
memory by this method. Remember that the byte value, although sent
to memory, also remains in the accumulator. It's more a copying than
a literal sending. To save time, if you are
sending a byte down to address 0 through 255 (called the "zero
page"), you can leave off the first two numbers: 1000 STA $07. This
is only for the first 256 addresses, but they get more than their
share of mail. Your machine's BASIC and operating system (OS) use
much of zero page for their own temporary flags and other things.
Zero page is a busy place, and there is not much room down there for
you to store your own ML pointers or flags (not to mention whole
routines).
Heavy
Traffic In Zero Page This second way to address, using
only two hex digits, any hex number between $00 and $FF or a decimal
number between 0 and 255, is called, naturally enough, zero page
addressing. It's pretty fast mail service: the deliverer has to
decide among only 256 instead of 65536 houses, and the computer is
specially wired to service these special addresses. Think of them as
being close to the post office. Things get in and out fast at zero
page. This is why your BASIC and operating system tend to use it so
often. These two addressing modes - absolute
and zero page - are very common ones. In your programming, you will
probably not use zero page as much as you might like. You will
notice, on a map of your computer's flags and temporary storage
areas, that zero page is heavily trafficked. You might cause a
problem storing things in zero page in places used by the OS
(operating system) or BASIC. Several maps of both zero page and
BASIC in ROM can be found in Appendix B. You
can find safe areas to store your own programs' pointers and flags
in zero page. A buffer (temporary holding area) for the cassette
drive or for BASIC's floating point numbers might be used only
during cassette loads and saves or during BASIC RUNs to calculate
numbers. So, if your flags and pointers were stored in these
addresses, things would be fine unless you involved cassette
operations. In any case, zero page is a popular, busy neighborhood.
Don't put any ML programs in there. Your main use of zero page is
for the very efficient "indirect Y" addressing we'll get to in a
minute. But you've always got to check your computer's memory map
for zero page to make sure that you aren't using bytes which the
computer itself uses. By the way, don't
locate your ML programs in page one (256-511 decimal) either. That's
for the "stack," about which more later. We'll identify where you
can safely store your ML programs in the various computers. It's
always OK to use RAM as long as you keep BASIC programs from putting
their variables on top of ML, and keep ML from writing over your
BASIC assembler program (such as the Simple
Assembler).
Immediate Another
very common addressing mode is called immediate addressing - it
deals directly with a number. Instead of sending out for a number,
we can just shove it immediately into the accumulator by putting it
right in the place where other addressing modes have an address.
Let's illustrate this:
1000 LDA $2500
(Absolute mode) 1000 LDA #$09
(Immediate mode)
The first example
will load the accumulator with whatever number it finds at address
$2500. In the second example, we simply wanted to put a 9 into the
accumulator. We know that we want the number 9. So, instead of
sending off for the 9, we just type a 9 in where we would normally
type a memory address. And we tack on a # symbol to show that the 9
is the number we're after. Without the #, the computer will load the
accumulator with whatever it finds at address number 9 (LDA $09).
That would be zero page addressing, instead of immediate
addressing. In any case, immediate addressing
is very frequently used, since you often know already what number
you are after and do not need to send for it at all. So, you just
put it right in with a #. This is similar to BASIC where you define
a variable (10 VARIABLE =9). In this case, we have a variable being
given a known value. LDA #9 is the same idea. In other words,
immediate addressing is used when you know what number you want to
deal with; you're not sending off for it. It's put right into the ML
code as a number, not as an address. To
illustrate immediate and absolute addressing modes working together,
let's imagine that we want to copy a 15 into address $4000. , (See
Program 4-2.)
Program 4-2. Putting An
Immediate 15 Into Absolute Address 4000.
|
0010
|
.BA $2000
|
; STARTING
ADDRESS OF THIS
|
|
0020
|
; ML PROGRAM IS
$2000 ("BA" = "BEGINNING ADDRESS").
|
|
0030
|
;
|
|
2000- A9
0F
|
0040
|
LDA
#15
|
; LOAD A WITH LITERALLY
15
|
2002- 8D 00
40
|
0050
|
STA
$4000
|
; STORE IT IN ADDRESS
4000
|
|
0060
|
;
|
|
|
0070
|
; NOTE THAT IN
SOME ASSEMBLERS YOU CAN
|
|
0080
|
; SWITCH BETWEEN
HEX AND DECIMAL. THE
|
|
0090
|
; 15 IS DECIMAL,
THE 4000 IS HEX. A
|
|
0100
|
; LITERAL HEX 15
WOULD BE WRITTEN #$15.
|
|
0110
|
.EN
| Implied Here's an
easy one. You don't use any address or argument with this
one. This is among the more obvious modes.
It's called implied, since the mnemonic itself implies what is being
sent where: TXA means transfer X register's contents to the
Accumulator. Implied addressing means that you do not put an address
after the instruction (mnemonic) the way you would with most other
forms of addressing. It's like a
self-addressed, stamped envelope. TYA and others are similar
short-haul moves from one register to another. Included in this
implied group are the SEC, CLC, SED, CLD instructions as well. They
merely clear or set the flags in the status register, letting you
and the computer keep track of whether an action resulted in a zero,
if a "carry" has occurred during addition or subtraction,
etc. Also "implied" are such instructions as
RTS (ReTurn from Subroutine), BRK (BReaK), PLP, PHP, PLA, PHA (which
"push" or "pull" the processor status register or accumulator onto
or off the stack). Such actions, and increasing by one
(incrementing) the X or Y register's number (INX, INY) or decreasing
it (DEX, DEY), are also called "implied." What all of these implied
addresses have in common is the fact that you do not need to
actually give any address. By comparison, an LDA $2500 mode (the
absolute mode) must have that $2500 address to know where to pick up
the package. TXA already says, in the instruction itself, that the
address is the X register and that the destination will be the
accumulator. Likewise, you do not put an address after RTS since the
computer always memorizes its jump-off address when it does a JSR
(Jump to SubRoutine). NOP (No OPeration) is, of course, implied mode
too.
Relative One
particular addressing mode, the relative mode, used to be a real
headache for programmers. Not so long ago, in the days when ML
programming was done "by hand," this was a frequent source of
errors. Hand computing - entering each byte by flipping eight
switches up or down and then pressing an ENTER key - meant that the
programmer had to write his program out on paper, translate the
mnemonics into their number equivalents, and then "key" the whole
thing into the machine. It was a big advance when computers would
accept hexadecimal numbers which permitted entering 0F instead of
eight switches: 00001111. This reduced errors and fatigue.
An even greater advance was when the machines began
having enough free memory to allow an assembler program to be in the
computer while the ML program was being written. An assembler not
only takes care of translating LDA $2500 into its three (eightswitch
binary) numbers: 10101101 00000000 00100101, but it also
does relative addressing. So, for the same reason that you can
program in ML without knowing how to deal with binary numbers - you
can also forget about relative addressing. The assembler will do it
for you. Relative addressing is used with
eight instructions only: BVS, BVC, BCS, BCC, BEQ, BMI, BNE, BPL.
They are all "branching" instructions. Branch on: overflow flag set
(or cleared), carry flag set (or cleared), equal, minus, not-equal,
or plus. Branch if Not-Equal, like the rest of this group, will jump
up to 128 addresses forward or backward from where it is or 127
addresses backward (if the result of the most recent activity is
"not equal"). Note that these jumps can be a distance of only 128,
or 127 back, and they can go in either direction. You specify where
the jump should go by giving an address within these boundaries.
Here's an example:
1000 LDX #$00 1002
INX 1003 BNE 1002 1005 BRK
(The X register will count up by ones until it hits 255
decimal and then it resets itself to zero.)
This is what you type in to create a ML FOR-NEXT loop. You are
branching, relative to address 1003, which means that the assembler
will calculate what address to place into the computer that will get
you to 1002. You might wonder what's wrong with the computer just
accepting the number 1002 as the address to which you want to
branch. Absolute addressing does give the computer the actual
address, but the branching instructions all need addresses which are
"offsets" of the starting address. The assembler puts the following
into the computer:
1000 A2 00 1002
E8 1003 D0 FD 1005 00
The odd thing about this piece of code is that "FD" at 1004.
How does FD tell the computer to Branch back to 1002? (Remember that
X will increment up to 255, then reset to zero on the final
increment.) $FD means 253 decimal. Now it begins to be clear why
relative addressing is so messy. If you are curious, numbers larger
than 127, when found as arguments of relative addressing
instructions, tell the computer to go back down to lower addresses.
What's worse, the larger the number, the less far down it goes. It
counts the address 1005 as zero and counts backwards
thus:
1005=0 1004 = 255 1003 =
254 1002 = 253
Not a very
pretty counting method! Luckily, all that we fortunate assembler
users need do is to give the address (as if it were an absolute
address), and the assembler will do the hard part. This strange
counting method is the way that the computer can handle negative
numbers. The reason it can only count to 128 is that the leftmost
bit is no longer used as a 128th's column. Instead, this bit is on
or off to signify a positive or negative number.
When you are using one of the branch instructions, you
sometimes branch forward. Let's say that you want to have a
different kind of FOR-NEXT loop:
1000 LDX
#0 1002 INX 1003 BEQ 100A 1005 JMP
1002 1008 BRK 1009 BRK 100A
BRK
When jumping forward, you often do not yet know the
precise address you want to branch to. In the example above, we
really wanted to go to 1008 when the loop was finished (when X was
equal to zero), but we just entered an approximate address (100A)
and made a note of the place where this guess appeared (1004). Then,
using the POKE function on the assembler, we can POKE the correct
offset when we know what it should be. Forward counting is easy.
When we finally saw that we wanted to go to 1008, we would POKE
1004, 3. (The assembler would have written a five because that's the
correct offset to branch to 100A, our original guess.)
Remember that the zero address for these relative
branches is the address immediately following the branch
instructions. For example, a jump to 1008 is three because you
count: 1005 a zero, 1006=1, 1007=2, 1008=3. All this confusion
disappears after writing a few programs and practicing with
estimated branch addresses. Luckily, the assembler does all the
backwards branches. That's lucky because they are much harder to
calculate.
Unknown Forward
Branches Also, the Simple Assembler will do one
forward ("not-yet-known") branch calculation for you. If you look at
the BASIC program listing of the Simple Assembler, you will see that
the pseudo-ops (fake operations) are located from line 241 up. You
could add additional forward-resolving pseudo-ops if you just give
them new names like F1 resolved later by R1. Alternatively, you can
type a guess in for the forward branches, as we just did in the
example above. Then, when you find out the exact address, simply
exit from the assembler, give 1004 as your starting address for
assembly, and write in BEQ 1008 and let the assembler calculate for
you. Either way, you will soon get the hang of forward
branching. We'll get into pseudo-ops later.
Essentially, they are instructions to the assembler (such as "please
show me the decimal equivalent of the following hex number"), but
which are not intended to be thought of as mnemonics which get
translated into ML object code. Pseudo-ops are "false" operations,
not part of the 6502 instruction set. They are requests to the
assembler program to perform some extra service for the
programmer.
Absolute,X And
Absolute,Y Another important addressing mode provides
you with an easy way to manipulate lists or tables. This method
looks like absolute addressing, but it attaches an X or a Y to the
address. The X or Y stands for the X or Y registers, which are being
used in this technique as offsets. That is, if the X register
contains the number 3 and you type: LDA 1000, X, you will LoaD the
Accumulator with the value (the number) which is in memory cell
1003. The register value is added to the absolute
address. Another method called Zero Page,X works the same
way: LDA 05,X. This means that you can easily transfer or search
through messages, lists, or tables. Error messages can be sent to
the screen using such a method. Assume that the words SYNTAX ERROR
are held in some part of memory because you sometimes need to send
them to the screen from your program. You might have a whole table
of such messages. But we'll say that the words SYNTAX ERROR are
stored at address 3000. Assuming that your screen memory address is
32768 (8000 hex), here's how you would send the
message:
1000
LDX #$00
(set the counter register to zero) 1002 LDA
$3000,X (get a letter at 3000 +
X) 1005 BEQ
$100E (if the character is a
zero, we've reached the end of message, so we end the
routine) 1007
STA $8000,X
(store a letter on the screen) 100A INX
(increment the counter so the
next letter in the message, as well as the next screen position, are
pointed to) 100B
JMP $1002
(jump to the load instruction to fetch the
next character) 100E BRK
(task
completed, message transferred)
This sort
of indexed looping is an extremely common ML programming device. It
can be used to create delays (FOR T =1 TO 5000: NEXT T), to transfer
any kind of memory to another place, to check the conditions of
memory (to see, for example, if a particular word appears somewhere
on the screen), and to perform many other applications. It is a
fundamental, all-purpose machine language technique.
Here's a fast way to fill your screen or any other area
of memory. This example uses the Commodore 64 Screen RAM starting
address. Just substitute your computer's screen-start address. This
is a full source code for the demonstration screen-fill we tried in
Chapter 1. See if you can follow how this indexed addressing works.
What bytes are filled in, and when? At ML speeds, it isn't necessary
to fill them in order - nobody would see an irregular filling
pattern because it all happens too fast for the eye to see it, like
magic. (See Program 4-3.) Compare this to
Program 1-2 to see the effects of using a different screen starting
address and how source code is an expansion of a
disassembly.
Program 4-3.
|
0010
|
|
.BA
40000
|
;(NOTICE IT'S
DECIMAL)
|
|
0020
|
;
|
|
|
|
0030
|
CHAR.A
|
.DE
$41
|
; CHARACTER
"A"
|
|
0040
|
;
|
|
|
9C40- A0
00
|
0050
|
|
LDY
#$00
|
; SET COUNTER TO
ZERO.
|
9C44- A9
41
|
0060
|
|
LDA
#CHAR.A
|
|
9C44- 99 00
04
|
0070
|
LOOP
|
STA
$0400,Y
|
|
9C47- 99 00
05
|
0080
|
|
STA
$0500,Y
|
|
9C4A- 99 00
06
|
0090
|
|
STA
$0600,Y
|
|
9C4D- 99 00
07
|
0100
|
|
STA
$0700,Y
|
|
9C50-
C8
|
0110
|
|
INY
|
; RAISE Y BY
1.
|
9C51- D0
Fl
|
0120
|
|
BNE
LOOP
|
; IF NOT ZERO, KEEP
GOING.
|
9C53-
60
|
0130
|
|
RTS
|
|
|
0140
|
|
.EN
|
| Indirect Y This
one is a real workhorse; you'll use it often. Several of the
examples in this book refer to it and explain it in context. It
isn't so much an address in itself as it is a method of creating an
address. It looks like this:
$4000 STA
($80),Y
Seems innocent enough. But
watch out for the parentheses. They mean that $80 is not the real
address we are trying to store A into. Instead, addresses $80 and
$81 are holding the address we are really sending our byte in A to.
We are not dealing directly with $0080 here; hence the name for this
addressing mode: indirect Y. If $80,81 have
these numbers in them:
$0080 01 $0081
20
and Y is holding a five, then the byte in A will end
up in address $2006! How did we get $2006?
First, we've got to mentally switch the numbers in $80,81. The
6502 requires that such "address pointers" be held in backwards
order. So visualize $80,81 as forming $2001, a pointer. Then add the
value in Y, which is five, and you get $2006.
This is a valuable tool and you should familiarize yourself
with it. It lets you have easy access to many memory locations very
quickly by just changing the Y register or the pointer. To go up a
page, add one to the number in $0081. To go down four pages,
subtract four from it. Combine this with the indexing that Y is
doing for you and you've got great efficiency. The pointers for this
addressing mode must be stored in zero page locations.
When an address is put into a pointer, you can see that
it was split in half. The address $2001 was split in the example
above. It's a two-byte number and ML terminology distinguishes
between the bytes by saying that one is the LSB (least significant
byte) and the other is the MSB (most significant byte). The $01 is
the least significant. To grasp what is meant by "significant,"
imagine chopping a decimal number such as 5015 in half. Since the
left half, 50, stands for fifty 100's and the right half stands for
15 ones, obviously the leftmost half, the 100's, is more
significant. Likewise, the left half of a two-byte hex number like
$2001 is the most significant byte. The $20 stands for 32 times 256
(in decimal terms). It's easy to multiply double-byte numbers by
decimal 256 by just adding one to the MSB. This would be a quick way
of moving through the "pages" in memory. The
other thing to remember about MSB,LSB is that they are reversed when
broken up and used as an address pointer: LSB,MSB.
Indirect X Not
often used, this mode makes it possible to set up a group of
pointers (a table) in page zero. It's like Indirect Y except the X
register value is not added to the address pointer to form the
ultimate address desired. Rather, it points to which of the pointers
to use. Nothing is added to the address found in the
pointer. It looks like this:
$5000 STA ($90,X)
To see it in
action, let's assume that part of zero page has been set up to point
to various parts of memory. A table of pointers, not just
one:
$0090 $00
Pointer #1 $0091 $04 (it
points to $0400) $0092 $05 Pointer
#2 $0093 $70
($7005) $0094 $EA Pointer
#3 $0095 $80 (pointing to
$80EA)
If X holds a two
when we STA $(90,X), then the byte in A will be sent to $7005. If X
holds a four, the byte will go to $80EA. All
in all, this has relatively little merit. It would be useful in rare
situations, but at least it's there if you should find you need
it.
Accumulator
Mode ASL, LSR, ROL, and ROR shift or manipulate the
bits in the byte in the accumulator. We'll touch on them in the
chapter on the instruction set. They don't have much to do with
addressing, but they are always listed as a separate addressing
mode.
Zero
Page,Y This can only be used with LDX and STX.
Otherwise it operates just like Zero Page, X discussed
above. There you have them, thirteen
addressing modes to choose from. The six you should focus on and
practice are: Immediate, Absolute (plus Absolute,Y and ,X), Zero
Page, and Indirect Y. The rest are either automatic (implied) or not
really worth bothering with until you have full command of the six
common and useful ones. Now that we've
surveyed the ways you can move numbers around, it's time to see how
to do arithmetic in ML.
Return to Table
of Contents | Previous
Chapter | Next
Chapter |
| |
|