A 2D array

Three rows of four words, one element reached by row and column, and a whole column added up by stepping ix a row at a time.

Twelve words laid out as three rows of four. The program adds up a whole column, which means stepping through memory a row at a time, and then reads one element by its row and column. The column total ends in iy and the element in de.

Every array up to here was one line of memory. A 2D array is the same line read in rows, and the two numbers you write in C as grid[row][col] have to be turned into one offset before anything can be read.

You need to know: the "Arrays, strings and ix" lecture and the "Addressing on the Z80" lecture. What is new here is the stride, the distance in bytes between one row and the next, which is what walking a column adds every pass.

The three .dw lines are one array of twelve words at 0x9000, and the rows exist only in how they are written down. Walking a column is ix parked on the top of it and moved on by STRIDE, eight bytes, at every pass: one row further down is one whole row of elements further along in memory. The assembler works COLS * 2 out while assembling, so nothing multiplies at run time, and iy comes out at 00DE, which is 222, from 2, 20 and 200.

add ix, de is the only way to move an index register by an amount the program computed, because the displacement in (ix+0) is a constant written into the instruction. That is why ld de, STRIDE sits inside the loop: de is used to read the element two lines above it, so the stride has to be put back before the addition.

row * COLS + col is the element's number in that one line, and doubling it turns a number of elements into a number of bytes, which is what the address arithmetic actually needs. COLS is 4 here, so row * COLS is two add hl, hl, and the doubling for the element size is a third. A number of columns that is not a power of two would need the shift and add routine from Multiply and divide, which is what makes a power of two the size every 8 bit program picks for a grid.

de comes out at 00C8, which is 200, the second element of the last row. Reading it takes two instructions, ld e, (hl) and then ld d, (hl) after an inc hl, because a word is two bytes and every load here moves one.

Walking a row would be the same loop with inc hl twice and no add at all, since the elements of a row sit next to each other. A column is the direction the array is not laid out in, and it costs one addition per pass to say so.

Try changing ld c, 1 to ld c, 3. iy comes out at 01BC, which is 444, and de at 0190, which is 400: one line moves both halves, because the column is a value both of them read.