RISC Lab: уровни 48–67

← Все карточки

В разработке. Уровни 48–67 заявлены в карте уровней и программе курса, но ещё не играбельны; сроки не объявлены. Этот обзор описывает учебную траекторию для планирования, а не готовые задания. Публичная страница модуля: хаб «Микроархитектура RISC».

Главная идея части 5

Первые 47 уровней дают ученику одну модель процессора: машину с одним аккумулятором, машинные команды и Verilog. Пятая часть переводит его на другую модель. Сначала школьник строит и понимает простой 32-битный RISC-процессор, выполняющий команды последовательно (48–60), затем разбирается, как ускорить его конвейером, сохранив правильность программ (61–67).

Часть начинается не с форматов инструкций, а с вопроса «зачем понадобилась другая архитектура». Уровень 48 ставит старую и новую машины рядом: одну и ту же арифметическую задачу ученик решает дважды и сам видит, что даёт набор регистров вместо одного аккумулятора.

Три этапа

  • 48–51. От аккумулятора к RISC. Отдельные компоненты: регистровая модель, регистровый файл, 32-битное АЛУ, константа внутри команды.
  • 52–60. От блоков к работающему процессору. Память, ветвления, циклы, функции, сборка полного однотактного ядра учебного RV32I-подмножества и вывод его на плату.
  • 61–67. От последовательного исполнения к конвейеру. Пять стадий, конвейерные регистры, конфликты данных и управления, измерение ускорения.

Этап 1–2: однотактное ядро (48–60)

  • 48 «Прощай, аккумулятор!» Сравнение аккумуляторной и регистровой машин на одной задаче. Ученик записывает 42 в x5, пробует изменить x0 и убеждается, что там всегда ноль, читает два регистра через два порта одновременно. Цель: понять, зачем нужна регистровая архитектура, до разговоров о форматах команд.
  • 49 «Анатомия R-команды» Команда add x5, x6, x7 разбирается на поля rd, rs1, rs2. Ученик собирает RegFile32: 32 регистра, декодер записи, два порта чтения; запись происходит по фронту такта, x0 изменить нельзя.
  • 50 «АЛУ на 32 бита» Знакомый сумматор переносится на 32 бита. Арифметика (сложение, вычитание), логика (AND, OR, XOR), сдвиги, знаковое и беззнаковое сравнение, флаг Zero для будущих ветвей. Проверка на граничных значениях.
  • 51 «Константа внутри команды» Команда addi x1, x0, -1: второй операнд лежит прямо в машинном слове. Ученик собирает SignExt32 (12 бит расширяются до 32 со знаком), разбирает I-формат, вручную раскладывает команду на поля и сверяет результат с мини-ассемблером.
  • 52 «Память по правилам» Load/store-архитектура: арифметика работает только с регистрами, с памятью её связывают команды lw и sw. Адрес считается в АЛУ из регистра и смещения; выравнивание по четыре байта. Здесь ученик впервые выходит за пределы регистрового файла.
  • 53 «Шаг на четыре» PC+4 и первая условная ветвь BEQ: смещение в B-формате разрезано на части машинного слова. Ученик прослеживает, как меняется PC при переходе и при обычном выполнении.
  • 54 «Цикл на BNE» Счётчик, сравнение и BNE: первый цикл на собственной машине. Перед запуском ученик предсказывает число тактов, потом сверяет прогноз с трассой.
  • 55 «Сумма массива» Настоящая программа: база массива собирается через LUI+ADDI, проход по элементам через LW, сумма накапливается в регистре. Программа использует сразу память, ветвление и арифметику.
  • 56 «Вызов и возврат» JAL и JALR: адрес возврата кладётся в ra. Ученик пишет небольшую функцию, вызывает её и возвращается; вложенный вызов требует сохранить ra в памяти. Так появляется понятие стека.
  • 57 «Вложенные вызовы и стек» Одного ra на один вызов: функция A вызывает B — и адрес возврата затёрт. Ученик пишет пролог и эпилог (сохранение и восстановление ra через sw/lw), инициализирует sp и собирает вложенный вызов main → A → B: кадр растёт в памяти вниз, от старшего адреса к младшему. Защитное слово ниже стека ловит переполнение ошибкой ERR_DATA_ADDRESS_FORBIDDEN.
  • 58 «Собираем RV32I» Полное однотактное ядро учебного RV32I-подмножества: PC, память команд, декодер, регистровый файл, АЛУ, память данных, управление. Ученик соединяет блоки, подключает управляющие сигналы и запускает программы разных типов.
  • 59 «Knight Rider RISC» Бегущая строка на светодиодах через memory-mapped I/O и экспорт проекта для Tang Nano 9K. Процессор из симулятора получает физический выход, по образцу части 4.
  • 60 «Лаборатория верификации» Сценарная проверка: ученик сам формулирует воздействия и ожидания, проверяет промежуточные состояния и ловит подсаженный баг — тест, который проходит и на правильной, и на сломанной схеме, ничего не проверяет.

Контрольная точка после уровней 58–59. Ученик должен не просто запустить программу, а объяснить прохождение конкретной инструкции через процессор: что сделает декодер, какие управляющие сигналы откроются, куда пойдёт результат. Хорошая проверка: одна и та же программа (счётчик, Фибоначчи, сумма массива) даёт одинаковый результат на полном ядре и падает на нём же при отключённом forwarding, stall или flush — эти режимы пригодятся на третьем этапе.

Этап 3: конвейер (61–67)

  • 61 «Почему такт длинный» Критический путь однотактной машины: за один такт инструкция проходит все блоки подряд, и такт длинный. Отсюда карта пяти стадий IF, ID, EX, MEM, WB и идея разбить путь на участки.
  • 62 «Между стадиями» Четыре конвейерных регистра IF/ID, ID/EX, EX/MEM, MEM/WB: данные, управляющие сигналы и номер rd едут вместе. Ученик прослеживает, какие значения передаются между стадиями.
  • 63 «Пять команд в полёте» Чтение трассы: каскадная диаграмма, заполнение и опустошение конвейера, положение каждой команды на такте k. Конвейер заполняется не мгновенно: на краях выполнения есть накладные расходы, зато в середине машина завершает примерно одну инструкцию за такт. Отдельная инструкция при этом не становится быстрее.
  • 64 «Короткий путь» Зависимая пара add x5, x6, x7 / sub x8, x5, x9: вторая команда хочет прочитать x5 раньше, чем первая записала результат. RAW-конфликт и forwarding: результат идёт к АЛУ напрямую из поздней стадии, минуя запись в регистровый файл; для выбора источника нужны проверки зависимостей.
  • 65 «Пузырь загрузки» Пара lw x5, 0(x6) / add x7, x5, x8: forwarding не успевает, потому что результат lw появляется слишком поздно. Ученик реализует обнаружение load-use и остановку конвейера ровно на один такт; остановка не должна терять или повторять инструкции.
  • 66 «Призраки неверного пути» Условный переход: конвейер уже выбрал следующие инструкции, а условие ещё не разрешено. Flush удаляет команды неверно выбранного пути до того, как они изменят архитектурное состояние. Ученик проверяет условные и безусловные переходы и считает цену каждого взятого перехода.
  • 67 «Knight Rider Pipeline» Финальное конвейерное ядро: одна программа на однотактной и конвейерной машине, сравнение числа тактов с учётом остановок и сбросов, расчёт CPI, экспорт ZIP. Опорные формулы: Texec = Cycles × Tclk, для пяти стадий без особых ситуаций Cycles = Nretired + 4; сверху добавляются потерянные такты от stall и flush, считать нужно именно потерянные такты, а не число вставленных пузырей.

Контрольная точка после уровня 67. Ученик обосновывает ускорение расчётом и объясняет, почему на конкретной программе оно может оказаться небольшим: зависимости и переходы добавляют такты, а производительность определяется и числом тактов, и длительностью каждого такта.

Методическая граница

Цель части 5 — не «выучить архитектуру RISC-V». Цель точнее: ученик самостоятельно понимает устройство, работу и ограничения учебного 32-битного RISC-процессора, реализующего выбранное подмножество RV32I, а затем разбирается, как его ускоряет пятистадийный конвейер.

Две вершины стоит оценивать отдельно. К концу однотактного этапа (58) ученик уже построил настоящий процессор и имеет право считать задачу выполненной. Конвейер (61–67) — отдельная вершина: как он меняет работу машины, какие новые проблемы создаёт и как измеряется реальное ускорение. Не делайте конвейер обязательным условием признания результата на 58.

Вопросы для обсуждения

  1. Чем регистровая машина лучше аккумуляторной для выражения a = (b + c) × (d − e)? Сколько команд и промежуточных записей в памяти нужно каждой модели?
  2. Зачем архитекторы «потратили» один регистр на постоянный ноль? Какие команды упрощаются благодаря x0?
  3. Почему в B-формате смещение разрезано на части, а не лежит одним куском? Что это экономит в аппаратуре?
  4. Что случится, если убрать запись по фронту такта из регистрового файла и писать сразу?
  5. Почему forwarding спасает пару «сложение, затем вычитание», но не пару «загрузка, затем сложение»? Нарисуйте стадии обеих пар.
  6. Конвейер завершает около одной инструкции за такт. Значит ли это, что каждая инструкция выполняется за один такт?

Критерии оценки

  1. Вершина 1 (к уровню 59). Собрал: однотактное ядро запускает программу с памятью, ветвлением и вызовом функции. Объяснил: прослеживает одну инструкцию через все блоки и называет управляющие сигналы. Перенёс: объясняет, что изменится в ядре при добавлении новой инструкции.
  2. Вершина 2 (к уровню 67). Собрал: конвейерное ядро проходит ту же программу. Объяснил: читает каскадную диаграмму, называет конфликт по диаграмме (RAW, load-use, control) и способ его обработки. Перенёс: считает CPI для новой программы с учётом stall и flush и предсказывает, где ускорение будет малым.
  3. На протяжении части. Прогноз до запуска: на уровнях 54 и 63 ученик сначала предсказывает поведение машины (число тактов, диаграмму), потом сверяет с трассой. Держите этот ритм: расхождение прогноза с наблюдением — лучший повод для разбора.