Уровень 65: Пузырь загрузки
Задание
Цель: собрать hazard.v (учёт того, использует ли команда rs1/rs2: не читают rs1 только lui и jal; rs2 — R, sw и ветки), hold next-PC, IfId.en = ~stall, IdEx.clr = stall — всё под гейтом stall_en. Форвардинг 64-го закрывает почти все RAW — кроме одного: lw из EX/MEM не форвардится, потому что данные ещё в памяти. Если команда использует результат lw на следующей ступени (соседняя), спасти такт нельзя — нужен пузырь: детектор Hazard32 сравнивает rd lw в EX с операндами команды в ID, замораживает PC и IF/ID и ставит пузырь в ID/EX — ровно один такт простоя. Приёмочные программы платят ровно по одному пузырю после каждого lw; негатив stall_en=0 обязан исказить результат без ошибок.
Форвардинг 64-го закрывает почти все RAW — кроме одного: lw из EX/MEM не форвардится, потому что на этой стадии готов только адрес, а данные выйдут из памяти к концу MEM. Если соседняя команда использует результат lw, спасти такт нельзя — нужен пузырь: ровно один такт простоя.
Редактируемые файлы — pipetop.v и hazard.v; форвардинг и idex 64-го — read-only и обязаны остаться в тракте. Задания:
- Соберите Hazard32 (hazard.v).
stall = memReadE & rdE!=0 & (совпадение с rs1 при usesRs1 | совпадение с rs2 при usesRs2). Компараторы — как в fwd.v: not(xor) по биту + and-дерево. - Учтите, читает ли команда операнд. rs1 читают все, кроме lui и jal. rs2 читают только R, sw и ветки — соберите отрицание:
usesRs2 = !(islui | isjal | isOpImm | memRead | isJalr). Сравнение номеров без масок давало бы ложные пузыри. - Берите rdE и memReadE из payload IdEx (unpack qIx): после пузыря payload обнулён clr'ом, и повторного stall не случится — ровно один пузырь.
- Проведите пузырь. Гейт
stall_en: hold next-PC (добавьте stallG в hold-дерево),IfId.en = ~freeze & ~stallG(команда держится и заходит повторно),IdEx.clr = stallG & ~freeze(заморозка при err сильнее). - Посчитайте цену. Развёрнутая сумма четырёх слов платит ровно 4 пузыря (по одному на lw); мини-программа lw→add — 1 пузырь. Ручной прогон показывает капсулы «пузырь (stall)» на дорожке EX и счётчик nstall.
Решение для самопроверки
Сначала соберите сами. Решение ниже — контрольные точки.
Что должно получиться: приёмка exact-step проходит: развёрнутая сумма четырёх слов — 19 ретайров, 27 тактов (4 заполнения + 4 пузыря, nstall = 4); мини-программа lw→add — 5/10 с одним пузырём; негатив stall_en = 0 считает из мусорного значения lw без ошибок, но регистры обязаны отличаться от эталона.
Шаг 1 — детектор. eq1/eq2 — 5×(xor→not)+and; stall = memReadE & rdNz & ((usesRs1&eq1)|(usesRs2&eq2)). В топе: qMemR = qIx[27], qRd = qIx[35:31], usesRs1 = ~(isjal|islui), usesRs2 = ~(islui|isjal|isOpImm|memRead|isJalr).
Шаг 2 — проводка. stallG = stall & stall_en; hold-дерево: hold = freeze | isEnd | stallG; IfId.en = nfreeze & ~stallG; IdEx.clr = stallG & nfreeze.
Шаг 3 — контрольные числа. Развёрнутая сумма: 19 ретайров, 27 тактов (12 + 4 заполнения + 4 пузыря), nstall=4. Мини lw→add: 5/10 (5 + 4 + 1 пузырь), nstall=1. Негатив stall_en=0: сумма считается из мусорного значения lw — err=0, но регистры обязаны отличаться от эталона.
Проверь себя
Почему пузырь ставится именно в ID/EX, а не в EX?
Конфликтуют lw в EX и потребитель в ID: PC и IF/ID замораживаются (команда зайдёт повторно), а clr обнуляет ID/EX — в EX уходит пустота вместо мусорных операндов. Через такт lw сам доедет до MEM/WB, и форвардинг 64-го доставит значение.