Уровень 62: Между стадиями
Задание
Цель: разрезать тракт однотактного ядра 58 на пять стадий и поставить между ними четыре межстадийных регистра: IF/ID (pc и команда), ID/EX (операнды, управление, rd), EX/MEM (результат АЛУ, данные записи, pc), MEM/WB (значение записи, rd). Каждый регистр — dff-пачка с входами en/clr; en гасится при ошибке (заморозка), clr на этом уровне константа. Топ — PipeTop32: PC+4 → выборка → декод → АЛУ → память → запись, ошибочный доступ замораживает конвейер (en=0, hold PC, we-гейты), слово .end держит PC на месте. Приёмка — exact-step: состояние ядра после РОВНО refCycles тактов обязано совпасть с однотактным эталоном, и снова после +8 тактов. Приёмочные программы линейные: без веток (разрешение переходов — 66-й), без зависимостей ближе 4 команд (форвардинг — 64-й) и без load-use (пузырь — 65-й): конвейер 62 честно платит латентностью, за то и точен.
Конвейер 61-го исполнял программы на эталонном движке. Теперь вы строите конвейер сами — структурно, из готовых блоков ядра 58 и четырёх межстадийных регистров: IfId32 (между выборкой и декодом), IdEx32 (между декодом и АЛУ), ExMem32 (между АЛУ и памятью), MemWb32 (между памятью и записью). Название уровня буквальное: вся работа — между стадиями, надёжно перевозить payload команды от стадии к стадии.
Каждый регистр — dff-пачка с входами en (разрешение такта) и clr (сброс, на этом уровне константа). Идиома одна на все четыре: dd = ((d & en) | (q & ~en)) & ~clr, dff на каждый бит через generate.
Топ — PipeTop32: контракт фиксируется до 66-го уровня — (clk, fwd_en, stall_en, flush_en, switches_in, pc, err, leds). Enable-порты на 62 не подключаются к логике: форвардинг (64-й), пузырь загрузки (65-й) и сброс веток (66-й) появятся позже. Поэтому приёмочные программы линейные: без веток, без зависимостей ближе 4 команд (regfile читается в ID, пишется в WB — записи в пути нет) и без load-use.
Ключевые механизмы топа:
- Заморозка при ошибке.
err != 0→en=0всех четырёх регистров, hold PC, we-гейты DMEM/MMIO. Запись регфайла не гейтится: инструкция в WB старше сбойной — её результат обязан доехать. - Выход pc при ошибке — PC сбойной инструкции из payload ExMem (заморозка «откатывает» счётчик к месту сбоя, как у однотактного эталона).
- Останов .end. Слово
32'h0000006Fдержит PC на месте (eq-дерево на выборке). Повторные копии .end пишут в x0 — безвредно. - Valid-биты. После reset конвейер полон нулевых слов: слово 0 декодируется illegal. Старший бит каждого payload — «инструкция реальна»; illegal гейтится valid'ом — чистый fill без ложных ошибок.
Решение для самопроверки
Сначала соберите сами. Решение ниже — контрольные точки.
Что должно получиться: exact-step приёмка проходит: состояние ядра после ровно refCycles тактов бит-в-бит равно эталону (walk: 11 ретайров, refCycles = 15, PC останова 0x2C); негатив lw x6, 2(x5) даёт err = 1 с PC = 4 и заморозкой без побочных эффектов.
Шаг 1 — регистры. Ширины: IfId 65 (pc · instr · valid), IdEx 155 (pc · rs1_val · rs2_val · imm · rd · regWrite · aluSrc · memWrite · memRead · jal · jalr · lui · illegal · iSel[9] · aluOp), ExMem 106 (alu_y · store_val · pc · rd · regWrite · memWrite · memRead · illegal · valid), MemWb 39 (wdata · rd · regWrite · valid). Старший бит каждого payload — valid.
Шаг 2 — цепочка valid. В IfId valid-вход = константа 1 (выборка всегда реальна — PC при выборке не даёт пузырей); дальше valid едет через все регистры и гейтит illegal на входе err_unit: мусорное слово 0 после reset не поднимает err.
Шаг 3 — топ. PC+4 → IMEM → IfId → Decoder + Control + ImmGen + RegFile(чтение) → IdEx → Alu32 + 8 I-модулей (выбор I-модуля — one-hot iSel[9:0], посчитанный в ID и проехавший через IdEx), alu_y = jal/jalr ? pc+4 : lui ? imm : alu → ExMem → Dmem/Mmio (we гейтится заморозкой) + ErrUnit → wdata = memRead ? memRd : alu_y → MemWb → RegFile(запись).
Шаг 4 — контрольные числа. Приёмочная «walk» (12 слов): 11 команд = 11 ретайров, refCycles = 15 (11 + 4 fill), PC останова 0x2C. Память после прогона: 0x1000 = 3, 0x1008 = 9, 0x100C = 0x7F0, 0x1010 = 20; x10 = 3, x11 = 9.
Шаг 5 — негатив. lui x5, 0x1; lw x6, 2(x5); .end → DATA_MISALIGNED (err=1), PC = 4 (адрес lw из payload ExMem), заморозка без побочных эффектов: запись регфайла старшей команды доезжает, сбойная — нет.
Проверь себя
Зачем конвейеру valid-биты в payload?
После reset конвейер полон нулевых слов, а слово 0 декодируется как illegal: старший valid-бит каждого payload гейтит illegal на входе err_unit, поэтому заполнение идёт без ложных ошибок, а мусор не доезжает до err.