Index registers and addressing

The normal use of the X and Y registers is in ``indexed addressing,'' so they are called ``index registers.''

What happens with variables whose locations are determined at run-time? There are three categories of variables like this:

Processors in general may include any of a number of major mechanisms to deal with run-time calculation of variable addresses: The 6811 follows what is becoming pretty much standard practice in modern architectures of only providing what Motorola calls indexed addressing (though it actually meets the ``standard'' definition of based addressing).

How do you use indexed addressing? First, concepts relating the ideas to a high-level langauge.

Indexed Addressing and Structs

Suppose you have a C struct. You put a pointer to it in the index register, and use an offset to the field you want in the offset field. Notice that you never iterate through the fields of a struct (the language syntax doesn't even support it); you always know the offset at compile-time.

Something that makes this more convenient is the equ assembler directive. You can define symbols for all the offsets and for the size of the object, and refer to the symbols later.

Indexed Addressing and Arrays

We have three cases to deal with here:
  1. Global arrays with a starting address within the range addressable by the offset. You just put the array's address in the offset, and use the index register to offset into the array.
  2. Global arrays with a starting address too large for the offset. You have to manually add the starting address to the array index, and use an offset of 0. The thing is, lookup tables whose contents are set at assembly time have to be accessed like this.
  3. Local arrays. Must be treated like local a cross between local variables and arrays with addresses above $ff, see later.

Local Variables

We'll defer this one until we've talked about procedures and the stack a little.

Indexed Addressing with the hc11

To use indexed addressing, you say offset,x or offset,y. For instance ldaa 7,x will load the A accumulator from address 7+x: so if IX contains 42, you'll load from address 49.

You say


ldx #label
ldy #label

The index registers are capable of very little arithmetic. You can increment them, you can decrement them, you can add the B accumulator to either one, you can load them, you can store them, you can exchange them with D, you can transfer them back and forth to the stack pointer. That's pretty much it.

Example

Here's an example of a program which will copy a string from a source array to a destination array, while making sure it doesn't overflow the destination buffer. The source array is at the beginning of EEPROM, while the destination array is at the beginning of RAM. Hopefully, the comments will make what's happening clear. This example actually accomplishes the same function as the C strncpy function.


RAM    equ    0
EEPROM equ    $f800
BUFSIZ equ    16

       org    RAM
dst    rmb    BUFSIZ   * space for destination array

       org    EEPROM
src    fcc    'This is a too-long source string'
       fcb    0        * terminate the string with a null, just like C does

start  ldab   #BUFSIZ  * guard so we don't overwrite
       ldx    #src     * pointer to source
       ldy    #dst     * pointer to destination

loop   ldaa   0,x      * get a char from source string
       staa   0,y      * copy to destination string

       beq    done     * if that was the null, we just finished

       inx             * increment pointers
       iny

       decb
       bne    loop     * if we aren't out of room, continue

done
eloop  bra    eloop

       end    start

Another Example: a non-zero offset

Here's another example: this one isn't a complete program, it just shows how to compare a value in an array against the next value.


      ldaa   0,x      * get the number IX points to
      cmpa   1,x      * compare it against the next number

Once that's been done, the result of the comparison is in the condition codes (as usual).

Yet Another Example: arrays, and arithmetic in the offset

If we have an array in RAM, we can use IX to hold its index. So, if we have an array that we would have declared in C as


char awry[10];

then in HC11 assembler we would reserve space for this same array with


awry  rmb   10

Now, the way we would exectute the C statement


a = awry[ix];

(note the use of a as the destination of the assignment and IX as the array index) would be to use the IX register to hold the array index and use the instruction


    ldaa  awry,x

One last thing we can do is to put some arithmetic in the offset. So, for instance, suppose we have an array called awry, and we want to compare a element of the array against its neighbor at index+1. We'll use IX to contain the array index, and the code can look like


    ldaa awry,x
    cmpa awry+1,x

Note that there are some very severe restrictions on the arithmetic we can do in the offset: we can only have constants in there (so an offset of awry+a would not use the a accumulator), and we can't try to save the result of the expression. In other words, the expression is evaluated at assembly time, and can't change during execution.

Instruction Prefix Bytes

Using the y index register also gives us an opportunity to learn about a standard hack that manufacturers apply to their instruction sets to expand them beyond the limitations of their original design: a prefix byte.

Early members of the Motorola 6800 family only had a single index register: the x register. When they wanted to add a second index register, they had a problem: their were too many instructions using indexed addressing to just add that many new op codes to the instruction set. This left them with two choices: they could either cripple the new y index register, leaving it capable of doing less than the x index register, or they could put a prefix byte into the instruction set. A prefix byte is a byte that ``adjusts'' what the instruction does. So, ldaa 3,x assembles to a6 3, while ldaa 3,y assembles to 18 a6 3.

Indexed Addressing and the IO Segment

One very important use of indexed addressing on the HC11 is the case where you want to use the various bit-twiddling instructions (like bset, brset, and their friends) to examine or control devices. Unfortunately, the devices are in a block starting at $1000, and those instructions don't support extended addressing. So, you can set one of the index registers to point to $1000, and use a one-byte offset to specify the particular device control register.

An Example

Here's an example of the use of indexed addressing to copy a character string from one location to another - effectively, the C strcpy function. We'll let the source string be in EEPROM, and the destination string be in RAM.

RAM equ 0 EEPROM equ $f800 org RAM dst rmb 10 org EEPROM src fcc _The string_ fcb 0 start ldx #src ldy #dst strcpy ldaa 0,x stab 0,y inx iny tsta bne strcpy done bra done end start