Уровень 59: Knight Rider RISC
Задание
Цель: запустить финал однотактной линии — программу «Knight Rider»: бегущий огонёк на LED через MMIO. Новых инструкций и схем нет: адрес LED 0xFFFF_F000 строится как lui x10, 0xFFFFF, каждая sw x11, 0(x10) зажигает биты огонька и ложится строкой в MMIO-трейс. Последовательность за период — 1,2,4,8,16,32,64,128,64,32,16,8,4,2,1 — повторить дважды и остановиться на LED = 1. LED write-only (lw с него — ERR_WRITEONLY_READ), switches read-only (sw — ERR_READONLY_WRITE). После зачёта ваше ядро 58 уедет на плату Tang Nano 9K ZIP-кнопкой.
Финал однотактной линии: огонёк бежит по восьми светодиодам туда и обратно, как в сериале «Рыцарь дорог». Никаких новых команд и схем — это уровень чистого программирования, и его программа уедет на настоящую плату.
Вывод идёт через MMIO (memory-mapped I/O): периферия живёт в адресном пространстве памяти. LED — адрес 0xFFFF_F000, write-only: запись обычным sw зажигает биты огонька, но чтение с этого адреса запрещено (ERR_WRITEONLY_READ). Тумблеры — 0xFFFF_F004, read-only: запись туда даёт ERR_READONLY_WRITE. Каждая sw в LED ложится строкой в MMIO-трейс — проверка сверяет его попарно.
Задания уровня:
- Соберите адрес LED.
0xFFFF_F000не влезает в 12-битное immediate — верхняя половина строится черезlui x10, 0xFFFFF. - Заложите шапку. Граница
x13 = 128(стоп-маска0x80) и счётчик периодовx14 = 2. - Напишите период. Метка
period: огонёкx11 = 1; вправо (right) —sw,slli,bneдо0x80; отдельная запись0x80; влево (left) —srliдо нуля. Последовательность за период:1,2,4,8,16,32,64,128,64,32,16,8,4,2,1. - Повторите период дважды и остановитесь на
.end— LED должен остаться 1. - Прогоните и сверьте. «Проверить» сначала спросит число записей MMIO-трейса — посчитайте сами: 15 за период × 2. После зачёта ZIP-кнопка соберёт ваше ядро 58 в проект Gowin для Tang Nano 9K.
Решение для самопроверки
Сначала напишите программу сами. Решение ниже — контрольные точки.
Что должно получиться: Knight Rider завершается штатно: 115 тактов, 30 записей MMIO-трейса (15 за период × 2), финальный LED = 1, регистры x10 = 0xFFFF_F000, x11 = 1, x13 = 128, x14 = 0; после зачёта ядро уезжает ZIP-кнопкой на Tang Nano 9K.
Шаг 1 — адрес через lui. lui x10, 0xFFFFF: imm20 = 0xFFFFF попадает в биты [31:12], младшие — нули. Получается ровно 0xFFFF_F000. Частая ошибка — пытаться собрать адрес addi-ями: 12-битное знаковое immediate такое константы не покрывает.
Шаг 2 — эталонная программа. Два периода на счётчике x14; внутри периода огонёк x11 идёт вправо сдвигами влево до границы 0x80 и обратно сдвигами вправо до нуля:
lui x10, 0xFFFFF # 0: база LED 0xFFFFF000
addi x13, x0, 128 # 4: 0x80, стоп-граница
addi x14, x0, 2 # 8: счётчик периодов
period: addi x11, x0, 1 # 12
beq x14, x0, halt # 16
addi x14, x14, -1 # 20
right: sw x11, 0(x10) # 24
slli x11, x11, 1 # 28
bne x11, x13, right# 32 → 24
sw x11, 0(x10) # 36: 0x80
left: srli x11, x11, 1 # 40
beq x11, x0, period# 44 → 12
sw x11, 0(x10) # 48
beq x0, x0, left # 52 → 40
halt: .end # 56
Шаг 3 — контрольные числа. Прогон: 115 команд = 115 тактов, останов .end на PC = 56 такта не добавляет. MMIO-трейс — 30 записей: за период 15 (восемь вправо, запись 0x80, шесть влево), периодов два; финальный LED = 1. Регистры в финале: x10 = 0xFFFF_F000, x11 = 1, x13 = 128, x14 = 0. Задержки (пауза между шагами) — ученический вариант поверх эталона: внутренний счётный цикл. Эталон без задержек — для детерминированной длины трейса.
Шаг 4 — контракт MMIO. Пробы на эталонном runner: lw с 0xFFFF_F000 — ERR_WRITEONLY_READ (err 7), состояние замораживается; sw в 0xFFFF_F004 — ERR_READONLY_WRITE (err 6); невыровненный sw по 0xFFFF_F002 — ERR_DATA_ADDRESS_MISALIGNED (err 1), кратность проверяется раньше MMIO. Чтение тумблеров работает: lw x6, 4(x10) при тумблерах 0x01 вернёт 1 (байт в младших битах, старшие — нули).
Шаг 5 — ZIP. После зачёта кнопка экспорта проверяет сохранённое ядро 58 (parse, структурные проверки, сборку схемы) и собирает архив: 28 .v-файлов ядра, программа в rtl/ROM.v, делитель тактов до ≈3 Гц, обёртка с портами clk/top_sw/top_led, CST-пины и проект Gowin. Битстрим в архив не входит — соберите его в Gowin EDA сами. После прошивки тот же код исполняет физический чип.
Проверь себя
Почему sw по 0xFFFF_F002 — это ошибка выравнивания, а не запись в LED?
Проверка кратности 4 стоит раньше MMIO-декода: невыровненный адрес ловится ERR_DATA_ADDRESS_MISALIGNED до того, как схема вообще смотрит, на какую периферию он указывает. «Половинчатой» записи не существует.