Уровень 58: Собираем RV32I

Открыть уровень →

Задание

Цель: собрать однотактное ядро учебного RV32I-подмножества из блоков прежних уровней. Семь редактируемых файлов: top.v (склейка, 3 мьюкса, decode адреса), control.v (таблица управления структурными гейтами), decoder.v (нарезка полей), immgen.v (I/S/B/U/J), imem.v (4×ROM-линии), mmio.v (LED/switches), err_unit.v (единая точка ошибок, выход err[3:0]). Остальные 21 файл — read-only эталоны волн 1–4. Проверка — дифференциальная: ваши fib и isqrt(100)/isqrt(50) обязаны побитно совпасть с эталонным ядром, а негативы (lw по 0x1002/0x1003, сырое слово 0) — дать верный err-код с заморозкой. Ручная отладка — runner-бар: Шаг/Пуск по тактам, fib уже прошит в ROM, переключатели и LED живут на 0xFFFF_F004/0xFFFF_F000.

Интеграционный уровень: блоки прежних волн собираются в однотактное ядро учебного RV32I-подмножества. Семь редактируемых файлов-бойлерплейтов — каркасы с портами и комментариями:

  • top.v — склейка: тракт PC → IMEM → decode → regfile → ALU → DMEM/MMIO → WB, три мьюкса (ALUSrc, WB, next-PC) и decode адреса;
  • control.v — таблица управления структурными гейтами: один-hot декод восьми классов опкодов (LUI, OP, OP-IMM, LOAD, STORE, BRANCH, JAL, JALR; прочее — illegal) и aluOp-коды 0..9;
  • decoder.v — нарезка полей команды;
  • immgen.v — immediate по формату: I и B — инстансами read-only SignExt32 и BImm32, S/U/J — руками;
  • imem.v — 64 слова на четырёх builtin ROM-линиях: addr линии k = {pc[7:2], 2'bK}, байты слова little-endian;
  • mmio.v — LED (write-only, 0xFFFF_F000) и switches (read-only, 0xFFFF_F004, старшие биты — нули);
  • err_unit.v — единая точка ошибок с выходом err[3:0].

Остальные 21 файл — read-only эталоны волн 1–4 (regfile, alu, dmem, signext, восемь I-модулей, add32, bimm, pc). Инстанс RegFile32 назовите rf, Dmem32 — mem: по этим именам платформа показывает финальное состояние регистров и памяти.

err[3:0] — код ошибки текущей команды: 0 — нет; 1 — ERR_DATA_ADDRESS_MISALIGNED; 2 — ERR_ILLEGAL_INSTRUCTION; 3 — ERR_IMEM_ADDRESS_OUT_OF_RANGE; 4 — ERR_DATA_ADDRESS_OUT_OF_RANGE; 5 — ERR_IMEM_DATA_READ; 6 — ERR_READONLY_WRITE; 7 — ERR_WRITEONLY_READ. Проверка addr[1:0] живёт на границе CPU→DMEM до усечения адреса — в err_unit/top, не внутри dmem.v: модуль памяти видит только addr[9:0] и младшие биты проверить не может. При err ≠ 0 гейтятся сигналы записи (RegWrite, MemWrite) и next-PC держит значение — сам такт не гейтится никогда: clk обязан идти от порта напрямую.

Проверка — дифференциальная. Платформа сама прогоняет по вашей схеме шесть программ и сравнивает финальное состояние с эталонным ядром risc-run побитно: fib (64 такта), isqrt(100) (65), isqrt(50) (47), негативы lw 0x1002 и lw 0x1003 (err = 1, заморозка без эффектов) и сырое слово 0x00000000 в IMEM (err = 2 на PC = 0).

Для ручной отладки — runner-бар: fib уже прошит в ROM, кнопки Шаг/Пуск крутят такты, LED и переключатели живут на MMIO-адресах.

Решение для самопроверки

Сначала соберите тракт сами. Решение ниже — контрольный список.

Что должно получиться: дифференциальная проверка проходит побитно: fib (64 такта), isqrt(100) (65), isqrt(50) (47); негативы дают верные err-коды с заморозкой — lw по 0x1002 даёт err = 1, сырое слово 0 — err = 2 на PC = 0.

Шаг 1 — тракт по порядку. PC → IMEM → декодер → RegFile → ALU → DMEM/MMIO → запись в RegFile. Каждая стрелка — провода, каждая развилка — мьюкс. Порядок сборки: decoder → control → immgen → imem → mmio → err_unit, затем top.v.

Шаг 2 — три мьюкса. ALUSrc: второй операнд АЛУ — rs2 или imm (для branch — rs2, ALUOp = sub, чтобы zero считался от разности). WB: что пишется в rd — memRd, pc+4 (jal/jalr), imm (lui) или результат ALU/ветки OP-IMM. next-PC: приоритет jalr > jal > branch > +4; при err ≠ 0 — hold значения PC.

Шаг 3 — управление. Таблица по классам: у sw нет rd, у lw нет записи в память, у веток нет результата. cond = zero ^ funct3[0] — beq берётся при zero, bne при not(zero). Запись в x0 маскирует декодер RegFile с 49-го.

Шаг 4 — err_unit. Флаги по каталогу артефакта 2 §5, затем приоритетный кодировщик: код 1 — самый высокий приоритет. IMEM кончается на 256 байтах (64 слова) — pc[31:8] ≠ 0 даёт код 3. Окно данных до 0x107F, IMEM-окно 0x000–0x3FF — чтение данных из него даёт код 5.

Шаг 5 — приёмка. fib требует beq/add/addi; isqrt — ещё slt/sub и bne. Негативы требуют честного err_unit: lw по 0x1002 — err = 1 на PC = 8, регистры и память не меняются; слово 0 — err = 2 на PC = 0. Совпадение побитно с эталоном означает, что вы собрали машину, а не схему.

Проверь себя

Почему такт не гейтится при ошибке?

При err ≠ 0 гейтятся только сигналы записи (RegWrite, MemWrite) и next-PC держит значение PC. clk идёт от порта напрямую: заморозка состояния не должна ломать тактирование — иначе машина не сможет продолжить после анализа сбоя.