Вопросы по реализации простого эмулятора процессора - PullRequest
6 голосов
/ 02 марта 2010

Справочная информация: В конечном счете, я хотел бы написать эмулятор реальной машины, такой как оригинальный Nintendo или Gameboy. Однако я решил, что мне нужно начать где-то намного, намного проще. Мой советник / профессор по информатике предложил мне спецификации для очень простого воображаемого процессора, который он создал, чтобы сначала имитировать. Имеется один регистр (аккумулятор) и 16 кодов операций. Каждая инструкция состоит из 16 битов, первые 4 из которых содержат код операции, остальная часть является операндом. Инструкции приведены в виде строк в двоичном формате, например, «0101 0101 0000 1111».

Мой вопрос: В C ++, как лучше всего анализировать инструкции для обработки? Пожалуйста, помните мою конечную цель. Вот некоторые моменты, которые я рассмотрел:

  1. Я не могу просто обрабатывать и выполнять инструкции, когда я их читаю, потому что код самоизменяется: инструкция может изменить более позднюю инструкцию. Единственный способ обойти это - сохранить все изменения и для каждой инструкции проверить, нужно ли их применять. Это может привести к огромному количеству сравнений с выполнением каждой инструкции, что не очень хорошо. Итак, я думаю, что мне нужно перекомпилировать инструкции в другом формате.

  2. Хотя я мог бы проанализировать код операции как строку и обработать ее, есть случаи, когда инструкция в целом должна быть взята как число. Например, инкрементный код приращения может изменить даже часть кода операции в инструкции.

  3. Если бы я должен был преобразовать инструкции в целые числа, я не был бы уверен, как бы я мог разобрать только секцию кода операции или операнда в int. Даже если бы мне пришлось перекомпилировать каждую инструкцию на три части: всю инструкцию как целое, опкод как целое и операнд как целое, это все равно не решило бы проблему, поскольку мне, возможно, пришлось бы увеличивать целую инструкцию и позже проанализируйте затронутый код операции или операнд. Кроме того, мне нужно написать функцию для выполнения этого преобразования, или есть какая-то библиотека для C ++, которая имеет функцию преобразования строки в «двоичном формате» в целое число (например, Integer.parseInt (str1, 2) в Java)?

  4. Кроме того, я хотел бы иметь возможность выполнять такие операции, как сдвиг битов. Я не уверен, как этого добиться, но это может повлиять на то, как я буду выполнять эту перекомпиляцию.

Спасибо за любую помощь или совет, который вы можете предложить!

Ответы [ 4 ]

5 голосов
/ 02 марта 2010

Разобрать исходный код в массив целых чисел. Этот массив является памятью вашего компьютера.

Используйте побитовые операции для извлечения различных полей. Например, это:

unsigned int x = 0xfeed;
unsigned int opcode = (x >> 12) & 0xf;

извлечет четыре старших бита (здесь 0xf) из 16-битного значения, хранящегося в unsigned int. Затем вы можете использовать, например, switch(), чтобы проверить код операции и предпринять соответствующие действия:

enum { ADD = 0 };

unsigned int execute(int *memory, unsigned int pc)
{
  const unsigned int opcode = (memory[pc++] >> 12) & 0xf;

  switch(opcode)
  {
  case OP_ADD:
    /* Do whatever the ADD instruction's definition mandates. */
    return pc;
  default:
    fprintf(stderr, "** Non-implemented opcode %x found in location %x\n", opcode, pc - 1);
  }
  return pc;
}

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

1 голос
/ 02 марта 2010

Я думаю, что лучший способ - прочитать инструкции, преобразовать их в целые числа без знака и сохранить их в памяти, а затем выполнить их из памяти.

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

  2. Так как вы преобразование инструкции целых, эта проблема является спорной.

  3. Чтобы разобрать секции кода операции и операнда, вам нужно использовать битовое смещение и маскирование. Например, чтобы получить код операции, вы маскируете верхние 4 бита и сдвигаетесь вниз на 12 бит (instruction >> 12). Вы можете использовать маску, чтобы получить операнд тоже.

  4. Вы имеете в виду, что на вашей машине есть инструкции, которые меняют биты? Это не должно влиять на то, как вы храните операнды. Когда вы приступите к выполнению одной из этих инструкций, вы можете просто использовать операторы C ++ << и >>.

0 голосов
/ 02 марта 2010

Я создал эмулятор для нестандартного криптографического процессора. Я использовал полиморфизм C ++, создав дерево базовых классов:

struct Instruction  // Contains common methods & data to all instructions.
{
    virtual void execute(void) = 0;
    virtual size_t get_instruction_size(void) const = 0;
    virtual unsigned int get_opcode(void) const = 0;
    virtual const std::string& get_instruction_name(void) = 0;
};

class Math_Instruction
:  public Instruction
{
  // Operations common to all math instructions;
};

class Branch_Instruction
:  public Instruction
{
  // Operations common to all branch instructions;
};

class Add_Instruction
:  public Math_Instruction
{
};

У меня также было несколько заводов. Было бы полезно по крайней мере два:

  1. Фабрика для создания инструкции из текст.
  2. Фабрика для создания инструкции из опкод

Классы команд должны иметь методы для загрузки своих данных из источника ввода (например, std::istream) или текста (std::string). Также должны поддерживаться методы вывода следствия (такие как имя инструкции и код операции).

У меня было приложение для создания объектов из входного файла и помещения их в вектор Instruction. Метод executor будет запускать метод execute () `для каждой инструкции в массиве. Это действие относится к объекту листа инструкции, который выполнил подробное выполнение.

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

Пожалуйста, потратьте больше времени на разработку и обдумывание проекта, прежде чем писать его. Мне это показалось довольно сложной задачей, особенно реализация отладчика с поддержкой single-step и графического интерфейса.

Удачи!

0 голосов
/ 02 марта 2010

На всякий случай, если это поможет, вот последний эмулятор процессора, который я написал на C ++. На самом деле, это единственный эмулятор, который я написал на C ++.

Язык спецификации немного своеобразен, но это вполне респектабельное, простое описание виртуальной машины, возможно, очень похожее на виртуальную машину вашего профессора:

http://www.boundvariable.org/um-spec.txt

Вот мой (несколько чрезмерно спроектированный) код, который должен дать вам некоторые идеи. Например, в инструкции Giant Switch в um.cpp показано, как реализовать математические операторы:

http://www.eschatonic.org/misc/um.zip

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

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

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

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

В случае единой системы обмена сообщениями, описанной выше, машина определяется в терминах «пластин» с пространством для 32 бит. Очевидно, что они могут быть представлены в C ++ как 32-разрядные целые числа, поэтому моя реализация так и делает.

...