6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Fri Mar 29, 2024 11:04 am

All times are UTC




Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Indirect Addressing
PostPosted: Mon Jun 04, 2012 8:14 pm 
Offline
User avatar

Joined: Wed May 30, 2012 7:45 pm
Posts: 58
Location: Dallas, TX
I have been trying to wrap my brain around Indirect Y. I'm still very new to 6502 programming. I have been studying "Machine Code For Beginners" and it the example it gives is not very clear.

Code:
 
$4000 STA ($80), Y
 
; later it shows the values of these two addresses
$0080 01
$0081 20


The above code is explained as follows:

Quote:
$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. Hence the name for this addressing mode: indirect Y.

I have a strong background in C, so I understand pointers. But in this context I don't see the benefit of using this model.
Why would you need an address based on the value of a memory address? Especially when the value may be different at
run-time.

I'm clearly missing the point here. I know that this would allow you to use zero-page addressing right? Perhaps someone could expound on this for me. I would also appreciate a practical example of this address mode.

Thanks!

_________________
http://www.thestarrlab.com


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Mon Jun 04, 2012 8:37 pm 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 986
Location: near Heidelberg, Germany
Johnny Starr wrote:
Why would you need an address based on the value of a memory address? Especially when the value may be different at
run-time.


Just a short answer - think of pointers to structs in C. Say p points to a struct, and p->value to an entry in the struct. The equivalent in 6502 would be to take p, store it in $80/$81, load Y with the offset of value in the struct, and use indirect indexed addressing mode.

A common use case is to have subroutines working on some data structures, where the address of the data structure is given as parameters, like here:
Code:
    lda #<p
    ldy #>p
    jsr subroutine
    ...

subroutine:
    sta $80
    sty $81
    ldy #OFFSET_OF_VALUE_IN_P
    lda ($80),y
    ...


So especially when the value may change at runtime you want this addressing mode.

Hope this helps

André

_________________
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Mon Jun 04, 2012 8:41 pm 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 986
Location: near Heidelberg, Germany
Johnny Starr wrote:
Quote:
$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. Hence the name for this addressing mode: indirect Y.


BTW - this quote is wrong. $80 and $81 do NOT hold the address the byte in A is stored to. It is the address stored in $80/$81 PLUS the value of the Y register. So if Y contains $12, the address would be $2013 - add the $12 to the $2001 that is stored in $80/$81 in your example.

André

_________________
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Mon Jun 04, 2012 9:25 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8412
Location: Southern California
Quote:
But in this context I don't see the benefit of using this model.
Why would you need an address based on the value of a memory address? Especially when the value may be different at
run-time.

The fact that the address might be different at run time is why you don't just do STA abs,y. The STA ($80),Y lets you have the base address of an array in $80 & $81 as a variable, and the Y determines how far into the array the byte you want to access is. The Eyes & Liechty programming manual gives the example of wanting to put a hyphen in a particular column of a display. The array's base address depends on the line number, and the Y gives the column number. Obviously the line number, and therefore the base of the line's array of characters, will change every time you go to another line of the screen.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Mon Jun 04, 2012 9:49 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3328
Location: Ontario, Canada
Johnny Starr wrote:
Why would you need an address based on the value of a memory address?
Although Zero Page is physically a section of memory, it serves the function of registers. You may have noticed that, compared to other microprocessors, the 6502 seems to have comparatively few registers. In fact it has more, since Zero Page locations have register-like capabilities not featured in other CPUs. An example of this is Indirect, Y addressing.

fachat wrote:
So especially when the value may change at runtime you want this addressing mode.
As André and Garth point out, sometimes a program needs to use addresses that are not pre-determined. Instead they are computed at run-time -- by following a linked list, perhaps, or referencing some other data structure. A conventional microprocessor would typically use a 16-bit on-chip register to hold the computed value and cause it to address memory. A 6502 programmer would use a pair of zero-page bytes to achieve the same function (especially since the 6502 has no 16-bit registers except the PC)! :D

As for the Y indexing applied after the zero-page fetch, think of this as optional. After all, many programming situations don't require this extra operation. On the old-style NMOS 6502 the indexing can be effectively bypassed by using Indirect, Y with Y set to zero. For convenience (and to free up Y) the CMOS 6502 ("65C02") has a new address mode which is simply Z-pg indirect with no indexing.

-- Jeff

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Mon Jun 04, 2012 11:06 pm 
Offline

Joined: Sat Dec 13, 2003 3:37 pm
Posts: 1004
Since you're familiar with C, consider this:

Given:
Code:
int array1[10];
int array2[10];

int (*arrayPtr)[];

You can do:
Code:
arrayPtr = &array1;
int i = (*arrayPtr)[2];
arrayPtr = &array2;
i = (*arrayPtr)[2];

In 6502, that's effectively:
Code:
    lda #<array1  ; low byte of array1
    sta $80
    lda #>array1  ; high byte
    sta $81
    ldy #2        ; index into array
    lda ($80),y   ; i = (*arrayPtr)[2]

    lda #<array2  ; Similar for array2
    sta $80
    lda #>array2
    sta $81
    ldy #2
    lda ($80),y
    brk

array1 .byte 1,2,3,4,5,6,7,8,9,10
array2 .byte 1,2,3,4,5,6,7,8,9,10

Hopefully that makes more sense.


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Tue Jun 05, 2012 12:15 am 
Offline
User avatar

Joined: Wed May 30, 2012 7:45 pm
Posts: 58
Location: Dallas, TX
whartung wrote:
Hopefully that makes more sense.


It does. Thank you all!

I'm still not clear on how arrays work in 6502. Well, I'm pretty sure they aren't actual arrays but I can see how this could be used.

Are you limited to 2 addresses?

Could someone clarify this?

_________________
http://www.thestarrlab.com


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Tue Jun 05, 2012 1:17 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8412
Location: Southern California
Quote:
I'm still not clear on how arrays work in 6502. Well, I'm pretty sure they aren't actual arrays but I can see how this could be used.
I don't think anything could be said to disqualify them from being arrays, although with the 6502, the index into it is limited to 8 bits, unlike the 65816 which can have 16-bit indexes into arrays. To go beyond those requires more instructions to add the index to the base to come up with an address. In software, you can of course form any kind of data structure you could want.

Quote:
Are you limited to 2 addresses?
You can have as many as you want, assuming they fit in available memory.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Tue Jun 05, 2012 2:56 pm 
Offline
User avatar

Joined: Wed May 30, 2012 7:45 pm
Posts: 58
Location: Dallas, TX
I haven't really mapped out how many registers there are on the 6502. These concepts ares still very new to me. But, how might you handle memory collision?

For example, say I had a routine that resided in $2000 - $2010. Say this routine handled a simple
calculation for me on a regular basis. But I also have other logic going on elsewhere that uses indirect addressing? Say I have an array with some values, and the pointer that is produced matches an existing address, such as $2002.

Code:
; $80 = 01
; $81 = 20
; Y = 1

STA ($80), Y ; #=> $2002


This is why the address from runtime value concerns me.

_________________
http://www.thestarrlab.com


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Tue Jun 05, 2012 3:40 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8114
Location: Midwestern USA
Johnny Starr wrote:
I haven't really mapped out how many registers there are on the 6502

The 65(c)02 has three "general purpose" registers, .A (accumulator), .X and .Y. I say "general purpose" in quotes because, of the three, .A is more capable, insofar as it is the only register in which arithmetic and logical operations can be performed. .X and .Y are primarily of value as indices and counters.

Now, if you consider the 256 possible zero page locations, the use of the word "register" is expanded, as one can gang ZP locations to act as 16 bit registers.

Quote:
These concepts ares still very new to me.

Don't become discouraged. These concepts were new to all of us at one time.

Quote:
But, how might you handle memory collision?

In assembly language, it is the responsibility of the programmer to avoid memory collisions. You don't get the hand-holding provided by higher level languages (C is high level compared to assembly language). In a non-trivial program, you work out your memory usage before you start writing your code. Proper use of your assembler's capabilities can help out in that regard. Be sure to assign names to every memory location that you plan to use so you keep it all straight. Don't bury "magic numbers" (e.g., addresses and constants) in your code.

For example:
Code:
;clear a data record
;
         lda #0
         ldy #15
;
loop     sta $2000,y        ;clear record area
         dey
         bpl loop

is not good, since two "magic numbers" (15 and $2000) are embedded in the code. Also, those numbers don't really tell you much about what that code does.

A better way to do it is:
Code:
;clear a data record
;
         lda #0
         ldy #recsiz-1      ;bytes to clear -1
;
loop     sta record,y       ;clear record area
         dey
         bpl loop

In your code, you would define the record thusly (this is a record with two fields: name and number):
Code:
;here we define the individual field sizes...
;
namsiz   =10                ;name field size
numsiz   =6                 ;number field size
;
;
;here we define the record structure...
;
namfld   =0                 ;name field offset
numfld   =namfld+namsiz     ;number field offset
recsiz   =numfld+numsiz     ;computed record size
;
;
;here we define the record's location...
;
record   *=*+recsiz         ;allocate space for data record

The expression *=*+recsiz advances the assembler's "program counter" by recsiz bytes, thus reserving recsiz bytes for the record storage (some assemblers use an alternate syntax—the *=*+recsiz syntax is the MOS Technology recommended style). The next data area would start at *+recsiz, so the memory allocation is somewhat "automatic" in nature.

Generally speaking, the field size and record size definitions are made near the beginning of the program (often in an .INCLUDE file so the definitions can be used in more than one program) and the record storage allocation is made after the last line of code or data where uncommitted RAM would start.

To copy, say, the number to another location in memory, you'd code thusly:
Code:
         ldx #numsiz        ;bytes to copy
         ldy #numfld        ;field to copy
;
loop     lda record,y       ;get from record &...
         sta elsewhere,y    ;store elsewhere in RAM
         iny                ;bump index
         dex                ;decrement counter
         bne loop           ;get some more

To copy the entire record to another location that is determined at run time, you could do this:
Code:
;copy record to location pointed to by .X & .Y
;
         stx zpptr          ;store destination LSB
         sty zpptr+1        ;store destination MSB
         ldy #recsiz-1      ;bytes to copy
;
loop     lda record,y       ;get a byte &...
         sta (zpptr),y      ;put in new location
         dey
         bpl loop           ;next

Notice how we get the assembler to do most of the work in calculating bytes to copy, field from which to copy, etc. In reality, this is most of what C does for you when it comes to memory management and such. C simply assigns names to memory locations and (indirectly) registers that you want to use. And, as you know, you can get collisions in C through the careless use of pointers and other forms of indirection.

Quote:
For example, say I had a routine that resided in $2000 - $2010. Say this routine handled a simple calculation for me on a regular basis. But I also have other logic going on elsewhere that uses indirect addressing? Say I have an array with some values, and the pointer that is produced matches an existing address, such as $2002.

Code:
; $80 = 01
; $81 = 20
; Y = 1

STA ($80), Y ; #=> $2002

This is why the address from runtime value concerns me.

So use more than one pair of ZP locations as a pointer. The pointer stored at $80/$81 has no relationship to that stored at $82/$83, etc. You simply decide which ZP locations will be assigned to which routine. If there is no possibility of a given routine being called from within another routine that also uses ZP, use the same ZP pair in both routines (roughly equivalent to a union in C).

There are 256 ZP locations available, which amounts to 128 possible pairs in which to set up pointers. Just how many will you need?

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Tue Jun 05, 2012 7:54 pm 
Offline
User avatar

Joined: Wed May 30, 2012 7:45 pm
Posts: 58
Location: Dallas, TX
Wow, thanks for the wealth of information. I might have to go over this a few times in the future as I learn more about how this works.

I would like one thing clarified though. You mentioned this:

Quote:
There are 256 ZP locations available, which amounts to 128 possible pairs in which to set up pointers. Just how many will you need?

The collison concern was not about ZP locations. It was about the other locations in RAM.
Going back to my question. Even if I label my subroutine "my_routine", the memory address will still have a numeric value right? Say $2002 for example.

Am I to understand that when using ZP pointers, and $2002 is created based on the values of what is stored at a pair of ZP registers, that it would override what was currently in $2002?

Or, is it that $2002 is some sort of psuedoaddress?
I don't know much about the PC counter yet, so you may have answered this question in your examples. If that is the case, I'm not sure which portion that would be.

Thanks again!

_________________
http://www.thestarrlab.com


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Tue Jun 05, 2012 8:51 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8412
Location: Southern California
There is nothing magic about $2002. You will always have to be careful to keep any given part of your program from stepping on memory needed by another part of the program. That's a small part of the reason to use names for various variables and constants. It helps keep control of what's what. If the overhead is not a problem, you can also make routines for accessing arrays, routines that add limits to make sure you don't store to an address beyond the end of the space allotted to the particular array, or that you don't jump indirect through a byte pair beyond the end of an array of addresses, etc.; but it is not usually necessary or even desirable. Developing good programming habits from the beginning will keep you out of trouble. This is covered partly in my 6502 primer in the following sections:
section 17, "General Steps for a Successful Project"
section 18, "Program-Writing: Where Do I Start?"
section 19, on debugging
section 20, on programming tips

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Tue Jun 05, 2012 9:01 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10760
Location: England
Hi Johnny
Bear in mind that program and data (and stack) all share the same memory space. It's the same with C programming: you can put any value into a pointer and write into any memory location. If you start writing data to a function pointer you'll overwrite instructions, in just the way that you're concerned about. That can even be useful, but isn't normally done.

In assembly, you do all the memory management yourself. When you construct a pointer, you'll almost certainly keep it in zero page, or copy it there in order to use it. (A pointer is just an address)

Hope that helps
Ed


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Tue Jun 05, 2012 9:47 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8114
Location: Midwestern USA
Johnny Starr wrote:
Even if I label my subroutine "my_routine", the memory address will still have a numeric value right? Say $2002 for example.

It goes without saying that all memory locations have a numeric value. I think your confusion is in how that value gets established.

Quote:
Am I to understand that when using ZP pointers, and $2002 is created based on the values of what is stored at a pair of ZP registers, that it would override what was currently in $2002?

Only if your code writes on that location. For example:
Code:
         ldx #<$2002
         ldy #>$2002
         stx $80
         sty $81

merely sets locations $80 and $81 to point to location $2002. Specifically, the value $02 is written into $80 and $20 is written into $81. Nothing gets written to the memory cell at $2002 until you execute something like this:
Code:
         lda #data
         ldy #0
         sta ($80),y

at which time the value DATA gets written into $2002, replacing what was previously there. Similarly:
Code:
         lda #data
         ldy #1
         sta ($80),y

writes the value DATA into $2003. In other words, the MPU takes the address stored at $80 and $81 and adds to it the value in the .Y register. The result is the "effective address," which is where the actual store (or load or other) operation will occur. This technique is referred to as postindexed indirect addressing and is a very common idiom in the 6502 universe. Postindexed indirection is the assembly language analog to using a pointer in C.

Keep in mind that all memory is common, so it's your job to verify that code, data and uninitialized storage don't overlap. As Garth pointed out, good style and structure go a long way toward writing programs that work. If you are a C programmer, you are already familiar with the structured, top-down style that was demonstrated in the K&R whitebook. Those same basic techniques work in assembly language. All you are doing that C isn't is determining where in memory your code and data are going.

Quote:
I don't know much about the PC counter yet, so you may have answered this question in your examples. If that is the case, I'm not sure which portion that would be.

Understanding the program counter (PC) is basic to being able to write an assembly language program for any MPU. In the 65xx family, the PC is a 16 bit register that points to the address of the next instruction that is to be executed. The MPU loads the instruction opcode at the location pointed to by PC, determines what it is, and whether it needs an operand. If it does, the MPU increments PC to point to the operand, which may be one or two bytes. If two bytes, the least significant byte (LSB) is loaded, the PC is again incremented, and then the most significant byte (MSB) is loaded. The MPU carries out this current instruction, increments PC to point to the next instruction, etc. Execution normally proceeds linearly, but if a branch is taken or a subroutine called, PC is changed to point to the branch target or subroutine. Prior to calling a subroutine, the MPU will write the current value of the PC on the stack so it knows where to resume when the subroutine is exited.

In an assembly language program, you set the starting address for your program, using a directive such as *=$2000. In most assemblers, the asterisk (*) is the symbol for the PC and can be manipulated in various ways. Aside from the *=$2000 statement, you can advance the PC N bytes with the expression *=*+N, which I used in my previous post about defining a record. In general, once the assembler's notion of the PC has been set, it takes care of itself. The assembler knows how many bytes are occupied by each instruction and advances the PC as required. Until you get reasonably comfortable with how an assembler works and can write programs that assemble and execute without errors, don't obsess about what is going on. Just let the assembler do its job.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
 Post subject: Re: Indirect Addressing
PostPosted: Tue Jun 05, 2012 10:21 pm 
Offline
User avatar

Joined: Wed May 30, 2012 7:45 pm
Posts: 58
Location: Dallas, TX
(Click)

That was the sound of my brain getting it :wink:

Thank you all for your replies. I'm going to study quite a bit until I hit my next snag.

Thanks!

_________________
http://www.thestarrlab.com


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: