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
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 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).
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.
STARTING ADDRESS OF THIS
ML PROGRAM IS $2000 ("BA" = "BEGINNING ADDRESS").
| 2000- A9 0F
||; LOAD A WITH
|2002- 8D 00 40
||; STORE IT IN
; NOTE THAT IN SOME ASSEMBLERS YOU CAN
SWITCH BETWEEN HEX AND DECIMAL. THE
15 IS DECIMAL, THE 4000 IS HEX. A
LITERAL HEX 15 WOULD BE WRITTEN #$15.
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.
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
1003 BNE 1002
(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
1003 D0 FD
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:
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
1003 BEQ 100A
1005 JMP 1002
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:
#$00 (set the counter register
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.
||;(NOTICE IT'S DECIMAL)
||; CHARACTER "A"
| 9C40- A0 00
||; SET COUNTER TO ZERO.
|9C44- A9 41
| 9C44- 99 00
| 9C47- 99 00
| 9C4A- 99 00
| 9C4D- 99 00
| 9C50- C8
||; RAISE Y BY 1.
| 9C51- D0 Fl
||; IF NOT ZERO, KEEP GOING.
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:
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.
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.
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.
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