The Right Address
We've covered a lot of ground in the first five chapters of this book. You now have a pretty good idea of how your computer works, and you know what goes on inside your Atari's 6502 chip when a program is running. You now know the principles of the binary and hexadecimal number systems, and you know how to write, debug, load and save assembly language programs. But we've really just begun to explore the capabilities of 6502 assembly language.
The 6502 processor in your Atari computer is an incredibly versatile device. It has only seven registers, and it understands only 56 instructions. But with those limited facilities, it can do some amazing things. One reason the 6502 chip is so versatile is because it can access the memory locations in a computer in 13 different ways. In other words, the 6502 processor has 13 different addressing modes. In the world of assembly language, and addressing mode is a technique for locating and using information stored in a computer's memory.
In the programs presented in this book so far, we've used three addressing modes: implicit addressing, immediate addressing, and zero page addressing. In this chapter, we'll be examining all three of those addressing modes, along with the ten others that are available.
Every 6502 instruction must be using one of the addressing modes. Not one instruction is capable of using all of the addressing modes, and no addressing mode can be used by all the instructions. The instruction tells the processor what to do and the addressing mode tells the process what to do it with. On the following page is a complete list of addressing modes and the format of the operation so you can tell what mode you are using. These formats are standard for the 6502 microchip so they should be understood by most 6502 assemblers.
The 6502's Addressing Modes
The 13 addressing modes of the 6502 processor are:
ADDRESSING MODE FORMAT 1. Implicit (Implied)* RTS 2. Accumulator ASL A 3. Immediate* LDA #2 4. Absolute LDA $5000 5. Zero Page* STA $CB 6. Relative BCC LABEL 7. Absolute Indexed, X LDA $5000,X 8. Absolute Indexed, Y LDA $5000,Y 9. Zero Page, X LDA $CB,X 10. Zero Page, Y STX $CB,Y 11. Indexed Indirect LDA ($B0,X) 12. Indirect Indexed LDA ($B0),Y 13. Indirect JMP ($5000)
The three instructions marked by * asterisks are the ones we've used in this book so far. All three in "ADDNRS.SRC," the 8-bit addition program introduced a few chapters back, which we'll take another look at now:
The "ADDNRS" Source Program
10 ; 20 ; 8-BIT ADDITION PROGRAM 30 ; 40 *=$0600 50 ; 60 ADDNRS CLD ; IMPLIED ADDRESS 70 CLC ; IMPLIED ADDRESS 80 LDA #02 ; IMMEDIATE ADDRESS 90 ADC #02 ; IMMEDIATE ADDRESS 100 STA $CB ; ZERO PAGE ADDRESS 110 RTS ; IMPLIED ADDRESS
In this example, the three address modes used in the program are identified in the comments column. Let's look now at each of these address modes.
Implicit (or Implied) Addressing
(Lines 60, 70 and 110)
Format: CLD, CLC, RTS, etc.
When you use implicit addressing, all you have to type is a three letter assembly language instruction; implicit addressing does not require (in fact does not allow) the use of an operand.
The instruction in an implied address is thus similar to an intransitive verb in English; it has no object. The address it refers to (if it refers to an address at all) is not specified, but merely implied by the mnemonic itself. So no operand is required or allowed in implicit addressing. Op code mnemonics that can be used in the implicit addressing mode are BRK, CLC, CLD, CLI, CLV, DEX, DEY, INX, INY, NOP, PHA, PHP, PLA, PLP, RTI, RTS, SEC, SED, SEI, TAX, TAY, TSX, TXA, TXS, and TYA.
(Lines 80 and 90)
Format: LDA #02, ADC #02, etc.
When immediate addressing is used in an assembly language instruction, the operand that follows the op code mnemonic is a literal number, not the address of a memory location. So in a statement that uses immediate addressing, a "#" sign, the symbol for a literal number, always appears in front of the operand. When an immediate address is used in an assembly language statement, the assembler does not have to peek into a memory location to find a value. Instead, the value itself is stuffed directly into the accumulator. Then whatever operation the statement calls for can be immediately performed. Instructions that can be used in the immediate address mode are ADC, AND, CMP, CPX, CPY, EOR, LDA, LDX, LDY, ORA, and SBC.
Zero Page Addressing
Format: STA $CB, etc.
It isn't difficult to distinguish between a statement that uses immediate addressing and one that uses zero page addressing. In a statement that uses zero page addressing, the operand always consists of just one byte, a number ranging form $00 to $FF. And that number equates to an address in a block of RAM called page zero.
The "#" symbol is not used in zero page addressing because the operand in a statement that employs zero page addressing is always a memory location, never a literal number. So the operation called for in the statement is performed on the contents of the specified memory location, not on the operand itself. Zero page addresses use one-byte operands because that's all they need. As we just said, the memory locations they refer to are in a block of your computer's memory that's called, logically enough, page zero. And to address a memory location on page zero, a one-byte operand is all that's necessary.
Specifically, the memory block in your computer known as page zero extends from memory address $00 through memory address $FF. You could just as easily (and just as correctly) say that page zero extends from $0000 to $00FF. But it isn't really necessary to use those extra pairs of zeros when you want to refer to a zero page address. When you follow an assembly language instruction with a one-byte address, your computer knows that the address is on page zero. Since zero page addresses use memory saving one-byte operands, page zero is the high rent district in your Atari's RAM; it's such a desirable piece of real estate, in fact, that the people who designed your computer took most of it for themselves. Most of page zero is used up by your computer's operating system and other essential routines, and not much space has been left there for user written programs.
Later on in this book, in a chapter dedicated to memory management, we'll discuss the memory space available on page zero in more detail. For now, the most important fact to remember about page zero is that it's an address mode that uses a memory address on page zero as a one-byte operand. Instructions that can be used with zero page addressing are ADC, AND, ASL, BIT, CMP, CPX, CPY, DEC, EOR, INC, LDA, LDX, LDY, LSR, ORA, ROL, ROR, SBC, STA, STX, and STY.
New Addressing Modes
Now we'll describe the five 6502 address modes we haven't covered so far:
Format: ASL A
The accumulator addressing mode is used to perform an operation on a value stored in the 6502 processor's accumulator. The command ASL A, for example, is used to shift each bit in the accumulator by one bit position, with the leftmost bit (bit 7) dropping into the carry bit of the processor status (P) register. Other instructions that can be used in the accumulator addressing mode are LSR, ROL, and ROR.
Format: STA $5000
Absolute addressing is similar to zero page addressing. In a statement that uses absolute addressing, the operand is a memory location, not a literal number. The operation called for in an absolute address statement is always performed on the value stored in the specified memory location, not on the operand itself. The difference between an absolute address and a zero page address is that an absolute address statement doesn't have to be on page zero; it can be anywhere in free RAM. So an absolute address statement requires a two-byte operand, not a one-byte operand, which is all that zero page address requires.
This is what our ADDNRS.SRC program would look like if absolute addressing, instead of zero page addressing, were used:
The "ADDNRS" Source Program
(with absolute addressing in line 100)
10 ; 20 ;8-BIT ADDITION PROGRAM 30 ; 40 *=$0600 50 ; 60 ADDNRS CLD ; IMPLIED ADDRESS 70 CLC ; IMPLIED ADDRESS 80 LDA #02 ; IMMEDIATE ADDRESS 90 ADC #02 ; IMMEDIATE ADDRESS 100 STA $5000 ; ABSOLUTE ADDRESS 110 RTS ; IMPLIED ADDRESS
The only change that has been made in this program is the one in line 100. The operand in that line is now a two-byte operand, and that change makes the program one byte longer. But now the address in line 100 no longer has to be on page zero. Now it can be the address of any free byte in RAM.
Mnemonics that can be used in the absolute addressing mode are ADC, AND, ASL, BIT, CMP, CPX, CPY, DEC, EOR, INC, JMP, JSR, LDA, LDX, LDY, LSR, ORA, ROL, ROR, SBC, STA, STX, and STY.
Format: BCC NEXT
Relative addressing is an address mode used for a technique called conditional branching, a method for instructing a program to jump to a given routine under certain specific conditions. There are eight conditional branching instructions, or relative address mnemonics, in 6502 assembly language. All eight begin with "B", which stands for "branch to". Examples of the conditional branching instructions that use relative addressing are:
BCC (Branch to a specified address if the Carry flag is Clear.) BCS (Branch to a specified address if the Carry flag is Set.) BEQ (Branch to a specified address if the Zero flag is Set.) BNE (Branch to a specified address if the Zero flag is Clear.)
All eight of the conditional branching instructions will be described later on in this book in a chapter devoted to looping and branching.
What Comparison Instructions do
The eight conditional branching mnemonics are often used with three other instructions called comparison instructions. Typically, a comparison instruction is used to compare two values with each other, and a conditional branch instruction is then used to determine what should be done if the comparison turns out in a certain way. The three comparison instructions are:
CMP ("compare the number in the accumulator with …") CPX ("compare the value in the X register with …") CPY ("compare the value in the Y register with …")
Conditional branching instructions can also follow arithmetic or logical operations, and various kinds of testing of bits and bytes. Usually, a branch instruction causes a program to branch off to a specified address if certain conditions are met or not met. A branch might be made, for example, if one number is larger than another, if two numbers are equal, or if a certain operation results in a positive, negative, or zero value.
An Example of Conditional Branching
Here's an example of an assembly language routine that uses conditional branching:
AN 8-BIT ADDITION PROGRAM WITH ERROR CHECKING
10 ; 20 ;8-BIT ADDITION WITH ERROR CHECKING 30 ; 40 *=$0600 50 ; 60 ADD8BTS CLD 70 CLC 80 LDA $5000 90 ADC $5001 100 BCS ERROR 110 STA $5002 120 RTS 130 ERROR RTS
This is an 8-bit addition program with a simple error checking utility built-in. It adds two 8-bit values, using absolute addressing. If this calculation results in a 16-bit value (a number larger than 255), there will be an overflow error in addition, and the carry bit of the processor status register will be set. If the carry bit is not set, then the sum of the values in $5000 and $5001 will be stored in $5002. If the carry bit is set, however, this condition will be detected in line100, and the program will branch to the line labeled ERROR – line 130. At line 100, you could begin any kind of routine you wanted to: you might choose, for example, to write a routine that would print an error message on the screen. In this sample program, however, an error results only in an RTS instruction.
Absolute Indexed Addressing
Format: LDA $0500,X or LDA $0500,Y
An indexed address, like a relative address, is calculated by using an offset. But in an indexed address, the offset is determined by the current content of the 6502's X register or Y register. A statement containing an indexed address can be written using either of these formats:
How Absolute Indexed Addressing Works
When indexed addressing is used in a n assembly language statement, the contents of either the X register or the Y register (depending upon which index register is being used) are added to the address given in the instruction to determine the final address. Here's an example of a routine that makes use of indexed addressing. The routine is designed to move byte by byte through a string of ATASCII (Atari ASCII) characters, storing the string in a text bugger. When the string has been stored in the buffer, the routine will end. The text to be moved is labeled TEXT, and the bugger to be filled with text is labeled TXTBUF. The starting address of TXTBUF, and the ATASCII code number for a carriage return are defined in a symbol table that precedes the program.
ROUTINE FOR MOVING A BLOCK OF TEXT
(An Example of Indexed Addressing)
10 ; 20 ;ROUTINE FOR MOVING A BLOCK OF TEXT 30 ; 40 TXTBUF=$5000 50 EOL=$9B 70 ; 80 *=$600 90 ; 0100 TEXT .BYTE $54,$41,$4B,$45,$20,$4D,$45,$20 0110 .BYTE $54,$4F,$20,$59,$4F,$55,$52,$20 0120 .BYTE $4C,$45,$41,$44,$45,$52,$21,$9B 0130 ; 0140 DATMOV 0150 ; 0160 LDX #0 0170 LOOP LDA TEXT,X 0180 STA TXTBUF,X 0190 CMP #EOL 0200 BEQ FINI 0210 INX 0220 JMP LOOP 0230 FINI RTS 0250 .END
Testing For A Carriage Return
When the program begins, we know that the string ends with a carriage return (ATASCII $9B), as strings often do in Atari programs. As the program proceeds through the string, it tests each character to see whether it is a carriage return or not. If the character is not a carriage return, the program moves on to the next character. If the character is a carriage return, that means there are no more characters in the string, and the routine ends.
Zero Page, X Addressing
Format: LDA $CB, X
Zero page, x addressing is used just like absolute indexed, x addressing. However, the address used in the zero page, x addressing mode must (logically enough) be located on page zero. Instructions that can be used in the zero page, x addressing mode are ADC, AND, ASL, CMP, DEC, EOR, INC, LDA LDY, LSR, ORA, ROL, ROR, SBC, STA, and STY.
Zero Page, Y Addressing
Format: STX $CB, Y
Zero page, y addressing works just like zero page, x addressing but can be used with only two mnemonics: LDX and STX. If it weren't for the zero page, y addressing mode, it wouldn't be possible to use absolute indexed addressing with the instructions LDX and STX – that's the only reason that this addressing mode exists at all.
There are two subcategories of indexed addressing: indexed indirect addressing, and indirect indexed addressing. Both indexed indirect addressing and indirect indexed addressing are used primarily to look up data stored in tables. If you think the names of the two addressing modes are confusing, you're not the first one with that complaint. I never could keep them sorted out myself until I dreamed up a little memory trick to help eliminate the confusion.
Here's the trick: Indexed indirect addressing, which has an "x" in the first word of its name, is an addressing mode that makes use of the 6502 chip's X register. Indirect indexed addressing, which doesn't have an "x" in the first word of its name, uses the 6502's Y register. Now we'll look at each of your Atari's two indirect addressing modes, beginning with indexed indirect addressing.
Indexed Indirect Addressing
Indexed indirect addressing works in several steps. First, the contents of the X register are added to a zero page address – not to the contents of the address, but to the address itself. The result of this calculation must always be another zero page address. When this second address has been calculated, the value that it contains, together with the contents of the next address, make up a third address. That third address is (at last) the address that will finally be interpreted as the operand of the statement in question.
An Example Of Indexed Indirect Addressing
An example might help clarify this process.
Let's suppose that memory address $B0 in your computer held the number $00, that memory address $B1 held the number $06, and the X register held the number 0. Here are those equates in an easier to read form:
$B0 = #$00 $B1 = #$06
X = #$00
Now let's suppose you were running a program that contained the indexed indirect instruction LDA ($B0, X). If all of those conditions existed when your computer encountered the instruction LDA ($B0,X), your computer would add the contents of the X register (a 0) to the number $B0. The sum of $B0 and 0 would, of course, be $B0. So your computer would go to memory address $B0 and $B1. It would find the number $00 in memory address $B0, and the number $06 in address $B1.
Since 6502 based computers store 16-bit numbers in reverse order, low byte first, your computer would interpret the number found in $B0 and $B1 as $0600. So it would load the accumulator with the number $0600, the 16-bit value stored in $B0 and $B1. Now let's imagine that when your computer encountered the statement LDA ($B0,X), 6502's X register held the number 04, instead of the number 00. Here is a chart illustrating those values, plus a few more equates that we'll be using shortly:
$B0 = #$00 $B1 = #$06 $B2 = #$9B $B3 = #$FF $B4 = #$FC $B5 = #$1C
X = #$04
If these conditions existed when your computer encountered the instruction LDA ($B0, X), your computer would add the number $04 (the value in the X register) to the number $B0, and would then go to memory addresses $B4 and $B5. In those two addresses, it would find the final address (low byte first, of course) of the data it was looking for, in this case, $1CFC.
A Rarely Used Mode
Indexed indirect addressing is not used in many assembly language programs. When it is used, its purpose is to locate a 16-bit address stored in a table of addresses stored on page zero. Since space on page zero is so hard to find, it's not very likely that you'll ever be able to store many data tables there. So it's not too likely that you'll ever find much use for indexed indirect addressing.
Indirect Indexed Addressing
Format: ADC ($C0), Y
Indirect indexed addressing is not nearly as rare as indexed indirect addressing. In fact, it is quite often used in assembly language programs. Indirect indexed addressing uses the Y register (never the X register) as an offset to calculate the base address of the start of a table. The starting address of the table has to be stored on page zero, but the table itself doesn't have to be. When an assembler encounters and indirect indexed address in a program, the first thing it does is peek into the page zero address that is enclosed in the parentheses that preceded the "Y". The 16-bit value stored in that address and the following address are then added to the contents of the Y register. The value that results is a 16-bit address, the address the statement is looking for.
An Example Of Indirect Indexed Addressing
Here's an example of indirect indexed addressing:
Your computer is running a program and comes to the instruction ADC ($B0), Y. It then looks into memory address $B0 and $B1. In $B0, it finds the number $00. In $B1, it finds the number $50. And the Y register contains a 4. Here is a chart that illustrates those conditions:
$B0 = #$00 $B1 = #$50
Y = #$04
If these states existed when your computer encountered the instruction ADC ($B0), Y, then your computer would combine the numbers $00 and $50, and would come up (in the 6502 chip's peculiar low byte first fashion) with the address $5000. It would then add the contents of the Y register (4 in this case) to the number $5000, and would wind up with a total of $5004. That number, $5004, would be the final value of the operand ($B0, Y. So the contents of the accumulator would be added to whatever number was stored in memory address $5004.
Once you understand indirect indexed addressing, it can become a very valuable tool in assembly language programming. Only one address, the starting address of a table, has to be stored on page zero, where space is always scarce. Yet that address, added to the contents of the Y register, can be used as a point to locate any other address in your computer's memory. As you become more familiar with assembly language, you'll have many opportunities to see how indirect addressing works. You'll find a few examples of the technique in programs in this book, and you'll run across many more examples in other assembly language programs.
Format: JMP ($5000)
In 6502 assembly language, unindexed indirect addressing can be used with only one mnemonic: JMP. One example of unindexed indirect addressing is the instruction JMP ($5000), which means, "Jump to the memory location stored in memory addresses $5000 and $5001."
The 'LIFO' Concept
The stack is what programmers sometimes call a "LIFO" (last in first out) block of memory. It works like a spring loaded stack of plates in a diner; when you put a number in the memory location on top of the stack, it covers up the number that was previously on top. So the number on top of the stack must be removed before the number under it, which was previous on top, can be accessed.
How 6502 Uses The Stack
The 6502 processor often uses the stack for temporary data storage during the operation of a program. When a program jumps to a subroutine, for example, the 6502 chip takes the memory address that the program will later have to return to, and pushes that address onto the top of the stack. Then, when the subroutine ends with a RTS instruction, the return address is pulled from the top of the stack and loaded into the 6502's program counter. Then the program can return to the proper address, and normal processing can resume. The stack is also used quite often in user written programs. Here is an example of a routine that makes use of the stack. You may recognize it as a variation on the 8-bit addition program that we've been using.
Don't try to run this program until you understand the stack and how to prevent the program from crashing.
AN ADDITION ROUTINE THAT MAKES USE OF THE STACK
10 ; 20 ;STACKADD.SRC 30 ; 40 *=$0600 50 ; 60 ;WHEN THIS PROGRAM BEGINS, TWO 70 ;8-BIT NUMBERS ARE ON THE STACK 80 ; 90 STKADD 100 CLD 105 CLC 110 PLA 120 STA $B0 130 PLA 140 ADC $B0 150 STA $C0 160 RTS 170 .END
This program is a simple, straightforward addition routine that shows how easy and convenient it can be to use the stack in assembly language programs. In line 110, a value is pulled from the stack and stored in the accumulator. Then in line 120, the value is stored in memory address $B0. In lines 130 and 140, another value is pulled from the stack, and added to the value now stored in $B0. The result of this calculation is then stored in $C0, and the routine ends. That's only one short example of many ways in which the stack can be used.
You'll find other ways to use the stack in later chapters of this volume. If you take care to manage the stack properly, in other words, if you clear the stack after each use, it can be a very powerful programming tool. But, if you mess up the stack while you're using it, you're surely bound for trouble!
Mnemonics that make use of the stack are:
PHA ("push the contents of the accumulator onto the stack")
PLA ("pull the top value off the stack and deposit it in the accumulator")
PHP ("push the contents of the P register onto the stack")
PLP ("pull the top value off the stack and deposit it into the P register")
JSR ("put the current PC on the stack and jump to address")
JSR ("put the current PC on the stack and jump to address")
RTS ("pull the return address off the stack and put it in the PC and increment it by one." This will cause execution to continue where it left off.)
The PHP and PLP operations are often included in assembly language subroutines so that the contents of the P register won't be wiped out during subroutines. When you jump to a subroutine that may change the status of the P register, it's always a good idea to start the subroutine by pushing the contents of the P register onto the stack. Then, just before the subroutine ends, you can restore the P register's previous state with a PHP instruction. That way, the P register's contents won't be destroyed during the course of the subroutine.
Return to Table of Contents | Previous Chapter | Next Chapter