Arrays, strings and ix

Arrays, strings and ix

Code and data share the Z80's 64 KB address space. Three common ways to lay out data in that space are an array of equal-sized values, a zero-terminated string, and a record whose fields have different jobs. In each case, the CPU still sees only bytes at addresses.

An array is a run of equal-sized values

An array puts values of the same size next to one another. A byte array advances one address per element. Put the address of the current element in hl; (hl) is that byte, and inc hl moves to the next one.

    .org 0x8000
    ld hl, numbers ; address of the first byte
    ld b, 6 ; six bytes to fill
    ld a, 1 ; value to write
fill:
    ld (hl), a ; write at the current address
    inc hl ; next byte in the array
    inc a
    djnz fill
    halt

        .org 0x9000

numbers: .ds 6 ; reserve six bytes; this directive writes no bytes

In this editor, fresh memory is displayed as 00, so the six reserved locations look like zeroes before the run. That is a property of the fresh emulator memory, not something .ds wrote. After the run they contain 01 02 03 04 05 06, and hl is 9006: one byte past the last location used.

The pointer step must match the element size. For an array of 16-bit words, each element occupies two adjacent bytes, so a pointer walk needs two inc hl instructions per element. (hl) itself does not know what kind of value the byte belongs to.

Reaching one element by its number

Sometimes the program has an element number in a register and needs its address directly. The Z80 has no instruction that combines an array address and a register index, so build the address yourself. For a byte array, widen an unsigned index from a into hl, then add the array's starting address.

    .org 0x8000
    ld a, 2 ; choose the third byte: index 2
    ld l, a
    ld h, 0 ; hl = 2, the widened unsigned index
    ld de, numbers
    add hl, de ; hl = address of the chosen byte
    ld a, (hl) ; a = 30
    halt

        .org 0x9000

numbers: .db 10, 20, 30, 40

For an array of words, first scale the index by two because every word occupies two bytes. add hl, hl doubles hl. The low byte of the chosen word is at the resulting address, and the high byte is one address later because Z80 words in memory are little endian.

    .org 0x8000
    ld a, 2
    ld l, a
    ld h, 0 ; hl = index
    add hl, hl ; hl = index times 2
    ld de, words
    add hl, de ; address of the third word
    ld c, (hl) ; low byte
    inc hl
    ld b, (hl) ; high byte, so bc = 300
    halt

        .org 0x9000

words: .dw 100, 200, 300, 400

When code uses every element in order, a pointer walk is usually simpler. Address calculation is for the occasions when the program genuinely needs one particular element.

Strings end with a zero byte

A string is a byte array whose bytes are character codes. A zero-terminated string uses a zero byte to mark the end of the text. .asciz "hi" defines exactly three bytes: the bytes for h and i, followed by a zero byte. The zero is the terminator; it is not a character in the text.

addressbyte in hexas a character
text0x68h
text + 10x69i
text + 20x20a space
text + 30x7Az
text + 40x21!
text + 50x00the terminator

A string loop reads a byte, tests it for zero, and only processes a nonzero byte. This one changes lowercase ASCII letters to uppercase in place. The label make_uppercase names the part that performs that change.

    .org 0x8000
    ld hl, text
next_character:
    ld a, (hl)
    or a
    jr z, done ; zero ends the string
    cp 'a'
    jr c, keep_character
    cp 'z' + 1
    jr nc, keep_character
make_uppercase:
    sub 32 ; ASCII 'a' is 32 above 'A'
    ld (hl), a
keep_character:
    inc hl
    jr next_character
done:
    halt

        .org 0x9000

text: .asciz "hi z!"

After the run, the text reads HI Z!. The space and ! remain unchanged because they are outside the ASCII range from a through z. cp 'z' + 1 compares with 123, so jr nc takes the path that keeps a byte when it is greater than z.

Records and ix

A record is a small fixed layout for values with different meanings. Say a player has an x position, a y position, and a number of lives. If ix holds the record's first address, the Z80 can read a byte at a fixed signed offset from it:

    ld ix, player
    ld a, (ix+1) ; read the byte one address after player: its y position

The +1 is written in the instruction, not held in a register. The offset can be from -128 through 127, which is plenty for a small record. Give field offsets names with equ so that the program says which field it uses.

X equ 0
Y equ 1
LIVES equ 2
SIZE equ 3

        .org 0x8000
        ld ix, players  ; first record
        ld b, 3         ; three player records
        ld a, 0         ; running total of y positions

sum_y:
    add a, (ix+Y)
    ld de, SIZE
    add ix, de ; first address of the next record
    djnz sum_y
    halt

        .org 0x9000

players:
    .db 1, 10, 0
    .db 2, 20, 0
    .db 3, 30, 0

a finishes at 0x3C, or 60. equ keeps the field offsets used by code in one named place. If the layout changes, update those names and SIZE; the actual record definitions must also gain, remove, or reorder their bytes to match the new layout. In the loop, ld de, SIZE and add ix, de advance ix by one record size.

Practice: a string walk and a record walk

text is a zero-terminated string. Count its characters, excluding the terminator, and leave the count in b. Use hl to walk the string.

    .org 0x8000
    ; your code here
    halt

        .org 0x9000

text: .asciz "z80!"
Show solution
    .org 0x8000
    ld hl, text
    ld b, 0
next_character:
    ld a, (hl)
    or a
    jr z, done
    inc b
    inc hl
    jr next_character
done:
    halt

        .org 0x9000

text: .asciz "z80!"

Each record below has an x byte followed by a y byte. Leave the sum of the three y values, 60, in a. Put the first record's address in ix, use Y for the field, and move ix by SIZE after each record.

X equ 0
Y equ 1
SIZE equ 2

        .org 0x8000
        ; your code here
        halt

        .org 0x9000

points:
    .db 4, 10
    .db 5, 20
    .db 6, 30
Show solution
X equ 0
Y equ 1
SIZE equ 2

        .org 0x8000
        ld ix, points
        ld b, 3
        ld a, 0

sum_y:
    add a, (ix+Y)
    ld de, SIZE
    add ix, de
    djnz sum_y
    halt

        .org 0x9000

points:
    .db 4, 10
    .db 5, 20
    .db 6, 30