Уровень 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 на себя

Задания уровня:

  1. Соберите слово bne в «Слове из полей» и разберите инверсию условия: у beq ветка = Branch & zero, у bne = Branch & not(zero). B-карта бит та же, что у beq уровня 53.
  2. Соберите цикл на addi и bne во вкладке «Ассемблер» и запустите его (Шаг/Пуск в блоке «Исполнение»).
  3. Убедитесь, что цикл завершается: x5 доходит до границы 10, последний bne уже не прыгает.
  4. Предскажите число тактов до запуска — первое «Проверить» спросит его и сверит с трассой.

Это первый полный 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 при этом уже исполнен в общем счёте — он просто не прыгает.