Уровень 54: Цикл на BNE
Задание
Цель: собрать классический for-цикл из трёх команд (addi-инициализация, addi-шаг, bne-обратный прыжок) на вкладке «Ассемблер», исполнить его по тактам и предсказать число тактов до останова — до первого «Проверить». Схема процессора не меняется: bne — близнец beq уровня 53 с инверсией условия, и в эталонном ядре он уже есть. Это первый уровень чистого программирования. Останов — .end: jal x0, 0 на себя, он такта не добавляет.
Схема процессора на этом уровне не меняется: у вас уже есть всё нужное. bne — близнец beq уровня 53: ветка выполняется, когда zero = 0, то есть регистры не равны — в эталонном ядре он уже есть. Зато меняется то, что вы пишете, — это первый уровень чистого программирования на собственной машине: исполнение идёт на однотактном ядре risc-run, интеграция ваших блоков в top.v — уровень 58.
Ваша программа — классический for-цикл (canon golden-программы counter):
addi x5, x0, 0 # счётчик = 0
addi x6, x0, 10 # граница = 10
loop:
addi x5, x5, 1 # счётчик += 1
bne x5, x6, loop # пока не равно — повторить
.end # останов: jal x0, 0 на себя
Задания уровня:
- Соберите слово
bneв «Слове из полей» и разберите инверсию условия: уbeqветка =Branch & zero, уbne=Branch & not(zero). B-карта бит та же, что у beq уровня 53. - Соберите цикл на
addiиbneво вкладке «Ассемблер» и запустите его (Шаг/Пуск в блоке «Исполнение»). - Убедитесь, что цикл завершается: x5 доходит до границы 10, последний bne уже не прыгает.
- Предскажите число тактов до запуска — первое «Проверить» спросит его и сверит с трассой.
Это первый полный golden-прогон: программа counter собирается и исполняется целиком. Предсказание тактов перед запуском — обязательная часть уровня: считайте сами, сколько команд исполнится при 10 итерациях, не забыв про bne на каждой итерации и про останов .end.
Решение для самопроверки
Сначала посчитайте такты на бумаге. Решение ниже поможет свериться.
Что должно получиться: цикл counter завершается: x5 доходит до границы 10, последний bne уже не прыгает; предсказанные 22 такта (2 инициализации + 10 итераций × 2) совпадают с трассой, PC замерает на адресе 16.
Шаг 1 — инверсия условия. У beq ветка = Branch & zero, у bne = Branch & not(zero). Всё остальное — смещение, PCSrc, адресация — совпадает. Частая ошибка: инвертировать забыли — и цикл либо не выполняется ни разу, либо не завершается никогда.
Шаг 2 — анатомия цикла. Две команды инициализации, тело — addi, условие — bne, назад ведёт отрицательное смещение до метки loop (в байтах от адреса самой bne: 8 − 12 = −4).
Шаг 3 — посчитайте такты. Однотактная машина исполняет одну команду за такт. Две инициализации + 10 итераций × (addi + bne) = 2 + 20 = 22 такта. Последний, десятый bne исполняется, но уже не прыгает (x5 = 10 = x6). Останов .end — это jal x0, 0, переход на себя: ядро видит неизменный PC и останавливается, не тратя такт, — PC замерает на адресе 16. Любая другая цифра (20 «без инициализации», 21 «счёт вниз», 23 «.end — отдельный такт») — повод перепроверить модель.
Шаг 4 — сверьтесь с трассой. Откройте трассу и найдите: каждый прыжок bne возвращает PC на 8; последний bne оставляет PC = 16; запись останова .end в трассе не увеличивает счётчик тактов. Сравните своё предсказание с фактическим числом тактов — совпадение означает, что вы управляете машиной, а не только собираете её.
Проверь себя
Почему останов .end не добавляет такт?
.end — это jal x0, 0 на себя: ядро видит, что PC не изменился, и останавливается, не исполняя переход как отдельный такт. Последний bne при этом уже исполнен в общем счёте — он просто не прыгает.