§ Модель видеопамяти

В этом главе уже начинается рассказ о том, как запрограммировать великий Спектрум (хвала ему), чтобы он мог находить видеоадреса. Я рассмотрю 2 независимых варианта, а то есть, вычисление видеоадреса для X=0..31, Y=0..23 в текстовом режиме и X=0..255, Y=0..191 в режиме графическом.
Видеопамять у Спекки странная, конечно, но вполне постижимая. Необходимо научиться транслировать сначала Y, поскольку он самый трудный для понимания.
Общий формат 16-битного видеоадреса:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 Y[7:6] Y[2:0] Y[5:3] X[4:0]
Здесь X и Y - это задаваемые параметры. В данном случае X может быть от 0 до 31, поскольку видеоадрес задается по 1 байту, который равен 8 точкам. А вот значение Y задается от 0 до 191 и важно следить, чтобы он не "уплыл" за 191, а то он может. Если это случится, то начнется сначала закрашивание области атрибутов, а потом вообще и данные попортить может.
Как можем заметить, распределена память очень неравномерно. Введем обозначения:
  • X[0..4] = X & 31
  • Y[0..2] = Y & 7
  • Y[3..5] = (Y>>3) & 7
  • Y[6..7] = (Y>>6) & 3
Если написать это в Си-стиле, то получится примерно следующее
1word get_video_address(byte X, byte Y) {
2
3    return (X & 31)              // Помещение X в биты 0..4
4         | ((Y & 7) << 8)        // Y[0..2] в биты 8..10
5         | (((Y>>3) & 7) << 5)   // Y[3..5] в биты 5..7
6         | (((Y>>6) & 3) << 11)  // Y[6..7] в биты 11..12
7         | 0x4000;
8}
В принципе ничего трудного, просто немного сдвиговой магии.

§ Вычисление курсора текста

Вычисление текстового курсора основывается на том же самом принципе, что и вычисление графического, но с тем отличием, что мы не будем заполнять биты 8-10 для видеоадреса, они будут равны 0. Значимыми битами останутся Y[3..7], то есть получается, что для курсора, который двигается по 8 пикселей, нижние 3 бита Y[0..2] просто опускаются. Потому новый вид будет такой:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 Y[3..4] 0 Y[0..2] X[0..4]
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
H L
Приведу текст программы
1word get_text_cursor_address(byte X, byte Y) {
2
3    return (X & 31)              // Помещение X в биты 0..4
4         | ((Y & 7) << 5)        // Y[0..2] в биты 5..7
5         | (((Y>>3) & 3) << 11)  // Y[3..4] в биты 11..12
6         | 0x4000;
7}
Как видно из кода, допустимый диапазон Y=0-31, но, конечно же, он реально равен 0-23,
т.к. превышение диапазона будет грозить артефактами и ошибками в данных.
Пришло время транслировать программу в ассемблер Z80
1; Вход:  B(Y=0..23), C(X=0..31)
2; Выход: HL(адрес)
3get_text_cursor_address:
4
5        ld      a, c
6        and     0x1F
7        ld      l, a    ; L = X & 31
8        ld      a, b
9        and     0x07    ; Нужно ограничить 3 битами
10        rrca            ; Легче дойти с [0..2] до позиции [5..7]
11        rrca            ; Если вращать направо
12        rrca            ; ... три раза
13        or      l       ; Объединив с 0..4 уже готовыми ранее
14        ld      l, a    ; Загрузить новый результат в L
15        ld      a, b    ; Т.к. Y[3..5] уже на месте
16        and     0x18    ; Его двигать даже не надо
17        or      0x40    ; Ставим видеоадрес $4000
18        ld      h, a    ; И загружаем результат
19        ret
Ассемблер конечно сложен для понимания, но эта программа не должна быть очень сложной.

§ Код вычисления позиции Y

Этот код почти полностью повторяет выше приведенный, с некоторыми поправками на то, что
Y теперь находится в диапазоне от 0 до 191.
1; Вход:  B(Y=0..191), C(X=0..31)
2; Выход: HL(адрес)
3get_graphics_address:
4
5        ld      a, c
6        and     0x1F    ; L[0..4] = X >> 3
7        ld      l, a    ; Y[0..2]
8        ld      a, b
9        and     0x07
10        ld      h, a    ; Установка => H[0..2]
11        ld      a, b    ; Y[3..5]
12        and     0x38
13        rlca
14        rlca
15        or      l
16        ld      l, a    ; Ставится в L[5..7]
17        ld      a, b
18        and     0xC0
19        rrca
20        rrca
21        rrca
22        or      h
23        or      0x40    ; H устанавливается на видеопамять
24        ld      h, a    ; Y[6..7] ставится в H[3..4]
25        ret
Здесь в этом коде немного непонятно сразу, но в целом он разбит на 5 частей
  • Сначала вычисляется X & 31
  • Потом записывается в регистр H (а точнее по смещению 8 адреса) биты Y[0..2]
  • Далее, рассчитываются и устанавливаются биты Y[3..5] в точку L[5..7] путем их смещения 2 раза влево
  • И после устанавливаются биты Y[6..7] в точку H[3..4] путем их смещения 3 раза вправо (соответствует битам адреса 11..13)
  • Записывается бит 14 в адрес
И видеоадрес готов!
Единственное, что я тут не рассказал, это как рассчитать точку. Но это делается очень просто. Для X[0..2], если мы берем попиксельно, ставится соответствие следующая таблица
Бит Hex    Bin        C      X
0 = 0x01 = 00000001 = 1<<0 | 7
1 = 0x02 = 00000010 = 1<<1 | 6
2 = 0x04 = 00000100 = 1<<2 | 5
3 = 0x08 = 00001000 = 1<<3 | 4
4 = 0x10 = 00010000 = 1<<4 | 3
5 = 0x20 = 00100000 = 1<<5 | 2
6 = 0x40 = 01000000 = 1<<6 | 1
7 = 0x80 = 10000000 = 1<<7 | 0
Тут уж кому как удобнее воспринимать, но в общем-то, это довольно просто.
В данной таблице, при установке бита 0, будет отображен самый правый пиксель (координата X = 7), при установке бита 7 - самый левый (X = 0). То есть, чтобы вывести пиксель, считаем слева направо - от 7 до 0, в таблице это видно визуально.