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 fcb assembler directive. You can define symbols for all the offsets and for the size of the object, and refer to the symbols later.

Let's look at a quick example of doing index addressing. We are just going to sum the values in a small table. First let's look at what the C pseudocode code loop would look like:


i = tablelen-1;
while (i != 0) {
     sum += table[i];
     i--;
}

And now the assembly code (we'll assume the table we are summing is in EEPROM so we don't have to worry about initializing it):



RAM     equ   $0010
EEPROM  equ   $f800
CONSTS  equ   $f900
TABLES  equ   $fa00
RESET   equ   $fffe

        org   RAM
cnt     rmb   1
sum     rmb   1

        org   CONSTS
tabst   fdb   TABLES
tablen  fcb   6

        org   TABLES
table   fcb   1, 2, 3, 4, 5, 6


        org   EEPROM
start   ldx   tabst       * Load start of table
	ldaa  0, X       
	staa  sum         * add value from table to sum
        ldaa  tablen
	deca
        staa  cnt         * save counter
loop    beq   done        * while (cnt != 0) {
        inx
	ldaa  sum
        ldab  0,x
	aba               *  sum = sum + table[x]
	staa  sum
        ldaa  cnt
	deca
        staa  cnt         * cnt--;
	bra   loop        * }
        
done    

mloop   bra   mloop

        org   RESET
	fdb   start
	

Now let's improve the code to make it more efficient and closer to the actual algorithm that we started with:



RAM     equ   $0010
EEPROM  equ   $f800
CONSTS  equ   $f900
TABLES  equ   $fa00
RESET   equ   $fffe
TABLELN equ   5

        org   RAM
sum     rmb   1

        org   CONSTS
tablen  fcb   TABLELN+1

        org   TABLES
table   fcb   1, 2, 3, 4, 5, 6


        org   EEPROM
start   clr   sum
	ldx   #table
        ldaa  tablen
loop    beq   done         * While (a != 0) {
        ldab  TABLELN,x
	addb  sum
	stab  sum          *   sum += table[a-1]
	dex
	deca               *   a--; 
	bra   loop         * }
        
done    

mloop   bra   mloop

        org   RESET
	fdb   start
	

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

You say offset,x or offset,y to use indexed addressing.

You say


ldx #label
ldy #label

to load a known address to the index register (as usual, you need to be careful about addressing modes). 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.

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....

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.

Here's another example of hand-compiling a program for the HC11 to sum the absolute values of the elements of a buffer. I'm going to use a buffer instead of an array here, because it translates more directly to assembly code.


sum = 0;
pnt = bufstrt;
while (pnt != bufend) {
    if (*pnt < 0)
        sum = sum - *pnt;
    else
        sum = sum + *pnt;
    pnt++;
}

And now the assembly code (we'll assume the array we are summing is in EEPROM so we don't have to worry about initializing it):



RAM     equ   $0010
EEPROM  equ   $f800
RESET   eqy   $fffe

        org   RAM
sum     rmb   1

        org EEPROM
bufstrt fcb 5, -2, 7, -3, 6, 5
bufend

start   clra                * initialize sum
        ldx   #bufstrt      * pnt = bufstrt
loop    cpx   #bufend       
        beq   done          * while (pnt != bufend ) {
        ldab  0,x
        bge   noneg         *   if (*pnt < 0) then
        sba                 *     sum = sum - *pnt;
        bra   eloop
noneg   aba                 *   else sum = sum + *pnt;
eloop   inx                 *   pnt++;
        bra   loop          * }

done    staa  sum           * store sum

mloop   bra   mloop

        org   RESET
	fdb   start