Вопросы относительно реализации простого эмулятора ЦП

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

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

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

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

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

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

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

6
задан 2 March 2010 в 14:35
поделиться

4 ответа

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

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

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;
}

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

5
ответ дан 17 December 2019 в 02:27
поделиться

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

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

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

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

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

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

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

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

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

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

0
ответ дан 17 December 2019 в 02:27
поделиться

Я создал эмулятор для специального криптографического процессора. Я использовал полиморфизм 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 ). Методы вывода следствия также должны поддерживаться (например, имя инструкции и код операции).

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

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

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

Удачи!

0
ответ дан 17 December 2019 в 02:27
поделиться

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

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

  2. Поскольку вы преобразуете инструкции в целые числа, эта проблема отпадает.

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

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

1
ответ дан 17 December 2019 в 02:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: