Уровень 55: Сумма массива
Задание
Цель: собрать и запустить первую полную программу: она читает массив 8 слов из памяти данных и суммирует его. Новая команда — lui (U-формат): imm20 в битах [31:12], значение = imm20 << 12; база массива 0x1000 собирается как lui x10, 0x1. Массив 3 1 4 1 5 9 2 6 залит платформой в DMEM до старта (виден в блоке «Исполнение»). Предскажите число тактов и сверьте побитно с эталоном.
Первый полный golden-прогон: программа читает массив из восьми 32-битных слов в памяти данных и складывает их. Всё нужное уже собрано: lw с уровня 52, beq с уровня 53, цикл с уровня 54. Новая команда одна — lui (U-формат): 12-битный immediate addi покрывает только −2048…2047, а база массива живёт по адресу 0x1000.
lui rd, imm20 кладёт 20 бит в старшую половину регистра: lui x10, 0x1 даёт x10 = 0x1 << 12 = 0x00001000 — ровно база DMEM. Массив 3 1 4 1 5 9 2 6 платформа заливает в память до старта (адреса 0x1000–0x101C, виден в блоке «Исполнение»), писать его в программу не нужно.
Каноническая программа array_sum:
lui x10, 0x1 # 0: x10 = 0x1000, база массива
addi x11, x0, 0 # 4: сумма
addi x12, x0, 8 # 8: счётчик (сверху вниз, 8 элементов)
loop: beq x12, x0, done # 12
addi x12, x12, -1 # 16
slli x14, x12, 2 # 20: байтовое смещение (i-1)*4
add x14, x10, x14 # 24
lw x15, 0(x14) # 28
add x11, x11, x15 # 32
beq x0, x0, loop # 36: → 12
done: .end # 40
Задания уровня:
- Соберите слово
luiв «Слове из полей» (на 55-м оно появилось в списке мнемоник) и разберите U-формат: imm20 в битах [31:12], младшие 12 бит — нули. - Соберите программу во вкладке «Ассемблер». Адрес слова: счётчик x12 уменьшается на 1 за итерацию, сдвиг
slli x14, x12, 2умножает его на 4 (байтовое смещение),addприбавляет базу. - Запустите по тактам: в блоке «Исполнение» видно окно памяти с массивом (подсветка — изменённые слова; программа их только читает, так что значения остаются на месте).
- Предскажите число тактов до запуска — первое «Проверить» спросит его и сверит с трассой.
Негативный сценарий уровня: замените шаг адреса на 1 (lw x15, 1(x10)). lw окажется по адресу 0x1001, не кратному 4. Эталонное ядро в этом месте останавливается с ошибкой ERR_DATA_ADDRESS_MISALIGNED и замораживает состояние: сбойная команда не исполняется, частичной суммы не появляется. Это не «сумма из кусков слов», а ошибка с заморозкой — сверяйте поведение с трассой, а не с интуицией.
Решение для самопроверки
Сначала посчитайте такты на бумаге. Решение ниже — контрольные точки.
Что должно получиться: array_sum даёт x11 = 31 (3 + 1 + 4 + 1 + 5 + 9 + 2 + 6) за 60 тактов (3 подготовительных + 8 итераций × 7 + 1); DMEM не изменяется, а lw по адресу 0x1001 останавливает машину с ERR_DATA_ADDRESS_MISALIGNED и заморозкой.
Шаг 1 — U-формат. У lui нет rs1 и funct3: слово состоит из imm20 [31:12], rd [11:7] и opcode 0110111. Значение регистра = imm20 << 12, младшие 12 бит — нули. lui x10, 0x1 = 0x00001537, x10 = 0x00001000.
Шаг 2 — компенсация знака в li. С 55-го псевдо-команда li rd, imm32 с большой константой разворачивается в lui + addi. У addi immediate знаковый: если бит 11 младшей части равен 1, прибавление «перенесёт» единицу в верхнюю половину, поэтому ассемблер заранее увеличивает imm20: hi = (v + 0x800) >> 12. Проверьте: li x10, 0x1000 — верх 0x1, низ 0, компенсации нет; li x10, 0x1FFF — верх уже 0x2, низ −1.
Шаг 3 — посчитайте такты. Подготовка — 3 команды (lui + две addi). Каждая итерация: beq-страж, addi-шаг, slli, add, lw, add и возвратный beq — 7 команд, 8 итераций. На последней итерации счётчик уже 0, страж beq берётся и ведёт на done. Итого 3 + 8 × 7 + 1 = 60 тактов. Останов .end (jal x0, 0) такта не добавляет, PC замерает на 40.
Шаг 4 — сверьте результат. Сумма массива 3+1+4+1+5+9+2+6 = 31: она окажется в x11. DMEM в конце не изменяется (программа только читает). Если поэкспериментируете с шагом 1, в трассе появится запись с ERR_DATA_ADDRESS_MISALIGNED на PC 28, а x11 останется нулевым — состояние заморожено, сумма не «испортилась», её просто нет.
Проверь себя
Почему базу массива нельзя записать одной addi?
12-битное знаковое immediate покрывает только −2048…2047. Базу 0x1000 собирает lui — он кладёт imm20 в старшие 20 бит регистра, а младшие при необходимости поправит addi (псевдо-команда li делает это сама).