chip8-sharp

Полный, протестированный эмулятор CHIP-8 на C#. Полный набор инструкций проверяется headless на общественных тестовых ROM - чистая библиотека без зависимости от рендеринга.

chip8-sharp
TL;DR

Полноценный эмулятор CHIP-8 на C#. Полный набор инструкций, таймеры и ввод, всё проверяется headless на сообществных тестовых ROM-ах. Чистая библиотека, без зависимости от отрисовки.

Обзор

chip8-sharp - это полноценный эмулятор CHIP-8, написанный на C#. CHIP-8 маленький - несколько десятков инструкций, простая память, экран 64 на 32 пикселя - и именно поэтому он классический "первый эмулятор". Но разница между игрушкой и проектом кроется в слове "полный": полный набор инструкций, таймеры и ввод, а не половина, "достаточная для одной игры".

Ядро - чистая библиотека без зависимости от отрисовки, ровно как в Game Boy. Благодаря этому всю машину можно запустить headless и прогнать через тестовые ROM-ы без единого окна, что превращает "вроде работает" в "проверено".

CHIP-8 - это обряд посвящения для каждого, кто хочет понять, как работает эмуляция. Он достаточно мал, чтобы уместить его целиком в голове, и достаточно настоящий, чтобы научить всему, что большие эмуляторы делают в большем масштабе: циклу fetch-decode-execute, памяти, таймерам, обработке ввода.

Его размер - достоинство, а не ограничение. Именно потому, что он мал, его можно сделать по-настоящему правильно, а не только "почти". Каждый опкод можно проверить отдельно, а у сообщества есть готовые ROM-ы, которые выводят "OK" или "FAIL" для каждой инструкции.

Один цикл: fetch, decode, execute

Вся машина крутится вокруг одного цикла. Опкод - это два байта, а отдельные биты говорят, что это за инструкция и на каких регистрах она должна работать. Декодирование состоит в том, чтобы вырезать нужные фрагменты из этих двух байтов и направить их в нужную ветку.

Interpreter.cs · csharp
ushort opcode = (ushort)(memory[PC] << 8 | memory[PC + 1]);
PC += 2;

ushort nnn = (ushort)(opcode & 0x0FFF);
byte x = (byte)((opcode & 0x0F00) >> 8);
byte kk = (byte)(opcode & 0x00FF);

switch (opcode & 0xF000) {
    case 0x1000: PC = nnn; break;
    case 0x6000: V[x] = kk; break;
    case 0x7000: V[x] += kk; break;
}

Анатомия опкода

Всё очарование CHIP-8 в том, что декодирование раскладывается на простые множители. Шестнадцатибитный опкод делится на фрагменты с фиксированным значением, и раз понятая эта анатомия превращает весь список инструкций в несколько простых правил.

Фрагменты шестнадцатибитного опкода

ФрагментБитыЗначение
nnnмладшие 12адрес в памяти
xбиты 8-11номер регистра
kkмладшие 8восьмибитная константа
➜
Совет

Если вы только начинаете с эмуляцией, начните именно с CHIP-8. Каждый опкод можно проверить отдельно, а у сообщества есть готовые ROM-ы, которые выводят "OK" или "FAIL" для каждой инструкции, так что с первого дня вы работаете с реальной обратной связью.

Эффект: полный набор, а не половина

На выходе - маленький, но отточенный эмулятор - полный набор инструкций, таймеры и ввод, проверенные headless на сообществных ROM-ах. Тот же приём, что и в Game Boy, отделение логики от вывода, делает так, что правильность доказана, а не заявлена. Это проект, маленький по замыслу, но по-настоящему законченный, а не брошенный на "хватит для одной игры".

Больше проектов

Другие работы из той же категории - посмотрите, как мы решаем похожие задачи.

Есть похожий проект?

Напишите нам - смета бесплатна и приходит в течение часа.