Декодирование и сопоставление кодов Chip 8 в C / C ++ - PullRequest
4 голосов
/ 08 июля 2011

Я пишу эмулятор Chip 8 как введение в эмуляцию, и я немного растерялся.По сути, я прочитал ПЗУ на чипе 8 и сохранил его в массиве символов в памяти.Затем, следуя руководству, я использую следующий код для извлечения кода операции с текущего счетчика программы (pc):

// Fetch opcode
opcode = memory[pc] << 8 | memory[pc + 1];

Код операции с чипом 8 по 2 байта каждый.Это код из руководства, которое я смутно понимаю как добавление 8 дополнительных битовых пространств в память [pc] (используя << 8), а затем объединение памяти [pc + 1] с ней (используя |) и сохранение результата в переменной opcode. </p>

Теперь, когда у меня есть изолированный код операции, я не знаю, что с ним делать.Я использую эту таблицу кодов операций, и я в основном потерян в отношении соответствия шестнадцатеричных кодов операций, которые я прочитал, идентификаторам кодов операций в этой таблице.Кроме того, я понимаю, что многие из кодов операций, которые я читаю, также содержат операнды (я предполагаю, что последний байт?), И это, вероятно, еще больше усложняет мою ситуацию.

Помощь?!

Ответы [ 4 ]

9 голосов
/ 08 июля 2011

Как правило, когда у вас есть инструкция, вам нужно ее декодировать.Например, из вашей таблицы кодов операций:

if ((inst&0xF000)==0x1000)
{
  write_register(pc,(inst&0x0FFF)<<1);
}

И предположим, что, поскольку вы обращаетесь к двум байтам на инструкцию, адрес, вероятно, является адресом слова (16 бит), а не байтовым адресом, поэтому я переместил его влево на один(вам нужно изучить, как кодируются эти инструкции, предоставленная вами таблица кодов операций не подходит для этого, ну, конечно, не нужно делать предположений).

Должно произойти намного больше, и я не знаю, смогу ли янаписал что-нибудь об этом в моих образцах github.Я рекомендую вам создать функцию выборки для извлечения инструкций по адресу, функцию чтения из памяти, функцию записи в память, функцию чтения из регистра, функцию записи из регистра.Я рекомендую вашу функцию декодирования и выполнения, декодирует и выполняет только одну инструкцию за раз.Обычное выполнение - просто вызывать его в цикле, оно дает возможность делать прерывания и тому подобное без особой дополнительной работы.Это также модулирует ваше решение.Путем создания функций fetch () read_mem_byte () read_mem_word () и т. Д.Вы модулируете свой код (с небольшими затратами производительности), что значительно упрощает отладку, поскольку у вас есть единственное место, где вы можете наблюдать за регистрами или обращениями к памяти и выяснять, что происходит или что не происходит.

На основе вашихВопрос, а где вы находитесь в этом процессе, я думаю, что первое, что вам нужно сделать перед написанием эмулятора, это написать дизассемблер.Быть набором инструкций фиксированной длины (16 бит), что делает его намного проще.Вы можете начать с какого-то интересного пункта в роме или с самого начала, если хотите, и расшифровать все, что видите.Например:

if ((inst&0xF000)==0x1000)
{
  printf("jmp 0x%04X\n",(inst&0x0FFF)<<1);
}

Только с 35 инструкциями, которые не должны занимать, кроме дня, может быть, целой субботы, будучи вашими первыми инструкциями по расшифровке (я полагаю, что это основано на вашем вопросе).Дизассемблер становится основным декодером для вашего эмулятора.Замените printf () на эмуляцию, еще лучше оставьте printfs и просто добавьте код, чтобы эмулировать выполнение инструкции, чтобы вы могли следить за выполнением.(в той же сделке разберите одну функцию инструкции, вызывайте ее для каждой инструкции, это становится основой для вашего эмулятора).

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

Также я бы назвал эту строку кода, которую вы предоставили, ошибочной или, по крайней мере, рискованной.Если memory [] является массивом байтов, компилятор может очень хорошо выполнить сдвиг влево с использованием математики размером в байты, в результате чего получается ноль, а затем нулевое значение со вторым байтом дает только второй байт.

В основномкомпилятор имеет право превратить это:

opcode = memory[pc] << 8) | memory[pc + 1];

В это:

opcode = memory[pc + 1];

Что совсем не работает, очень быстрое исправление:

opcode = memory[pc + 0];
opcode <<= 8;
opcode |= memory[pc + 1];

Избавит вас от головной боли.Минимальная оптимизация избавит компилятор от сохранения промежуточных результатов в оперативной памяти для каждой операции, что приведет к одинаковым (желаемым) результатам / производительности.

Симуляторы набора команд, которые я написал и упомянул выше, предназначены не для повышения производительности, а для удобства чтения, наглядности и, надеюсь, для обучения.Я бы начал с чего-то подобного, тогда, если производительность, например, представляет интерес, вам придется ее переписать.Этот эмулятор chip8, когда-то испытанный, будет задачей дня с нуля, поэтому, как только вы справитесь с этим в первый раз, вы можете переписать его, может быть, три или четыре раза в выходные, а не монументальное задание (чтобы переписать).(Большая часть одного заняла у меня выходные, по большей части. msp430 один, вероятно, больше походил на вечер или два, стоит работы. Правильно раз и навсегда установить флаг переполнения было самой большой задачей, и это произошло позже.).В любом случае, обратите внимание на такие вещи, как исходные коды, большинство, если не все, эти имитаторы набора команд рассчитаны на скорость выполнения, многие из них едва читаемы без достаточного количества изучения.Часто в большой степени управляемый таблицами, иногда много хитростей программирования на C и т. Д. Начните с чего-то управляемого, настройте его правильно, затем подумайте об улучшении его для скорости, размера, переносимости или чего-то еще.Эта микросхема выглядит как основанная на графике, поэтому вам также придется иметь дело с большим количеством рисования линий и другими битовыми манипуляциями на растровом изображении / экране / где угодно.Или вы можете просто вызвать API или функции операционной системы.По сути, этот чип 8 не является вашим традиционным набором инструкций с регистрами и перечнем режимов адресации и операций alu.

4 голосов
/ 08 июля 2011

В основном - замаскируйте переменную часть кода операции и найдите совпадение. Затем используйте переменную часть.

Например, 1NNN - это прыжок. Итак:

int a = opcode & 0xF000;
int b = opcode & 0x0FFF;
if(a == 0x1000)
   doJump(b);

Тогда игра должна сделать этот код быстрым, маленьким или элегантным, если хотите. Хорошее чистое веселье!

3 голосов
/ 13 января 2012

Различные процессоры хранят значения в памяти по-разному. Машины с прямым порядком байтов хранят в памяти такие числа, как $ FFCC, в таком порядке FF, CC. Машины с прямым порядком байтов хранят байты в обратном порядке CC, FF (то есть с первым «младшим концом»).

Архитектура CHIP-8 имеет порядок байтов, поэтому в коде, который вы будете выполнять, есть инструкции и данные, записанные в формате байтов.

В вашем утверждении "opcode = memory [pc] << 8 | memory [pc + 1];", не имеет значения, является ли ЦП хоста (ЦП вашего компьютера) прямым или прямым порядком байтов. Он всегда помещает 16-битное значение с прямым порядком байтов в целое число в правильном порядке. </p>

Существует несколько ресурсов, которые могут помочь: http://www.emulator101.com содержит учебник по эмулятору CHIP-8 вместе с некоторыми общими приемами эмулятора. Это тоже хорошо: http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/

2 голосов
/ 08 июля 2011

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

Ваш конечный автомат может в основном делать следующее:

  1. Получить первый кусочек кода операции, используя маску типа `0xF000. Это позволит вам «классифицировать» код операции
  2. На основе категории функции из шага 1, примените больше масок, чтобы либо получить значения регистра из кода операции, либо любые другие переменные, которые могут быть закодированы с кодом операции, который сузит фактическую функцию, которую нужно будет вызывать, как ну как аргументы.
  3. Когда у вас есть код операции и информация о переменной, выполните поиск в таблице функций фиксированной длины, которые имеют соответствующие обработчики, совпадающие с функциональностью кода операции и переменными, которые согласуются с кодом операции. В то время как вы можете, в своем автомате состояний, жестко кодировать имена функций, которые будут идти с каждым кодом операции, как только вы изолируете правильную функциональность, таблица, которую вы инициализируете с помощью указателей на функции для каждого кода операции, является более гибким подходом позволит вам легче изменять функциональность кода (т. е. вы можете легко переключаться между обработчиками отладки и «обычными» обработчиками и т. д.).
...