• Страница 1 из 1
  • 1
Создаем эмулятор приставки
Ang3LДата: Суббота, 2011-01-22, 18:06:29 | Сообщение # 1
Нет аватара у Ang3L
Бывалый
Киберюзеры
Сообщений: 88
Репутация: 2
Замечания: 0%
Не на форуме
Вероятно, многие программисты если и не мечтали, то хотя бы задумывались о написании собственного эмулятора какого-либо процессора. Возможно, некоторые даже экспериментировали с чем-то вроде Z80. Но не многие дошли до финальной реализации эмулятора.



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

Конец

Как бы это странно не было, а начну я с конца. Вот такая программа

Code
    OPTION BINARY   ; We want a binary file, not an HP48 one.
      ALIGN OFF       ; And we don't want auto alignement, as some
                      ; data can be made of bytes instead of words.
         
          LD  V0, 0
          LD  V1, 0
         
      LOOP:
          LD  I,  LEFT    ; We draw a left line by default, as the random number
                          ; is 0 or 1. If we suppose that it will be 1, we keep
                          ; drawing the left line. If it is 0, we change register
                          ; I to draw a right line.
         
          RND V2, 1       ; Load in V2 a 0...1 random number
         
          SE  V2, 1       ; It is 1 ? If yes, I still refers to the left line
                          ; bitmap.
         
          LD  I,  RIGHT   ; If not, we change I to make it refer the right line
                          ; bitmap.
         
          DRW V0, V1, 4   ; And we draw the bitmap at V0, V1.
         
          ADD V0, 4       ; The next bitmap is 4 pixels right. So we update
                          ; V0 to do so.
         
          SE  V0, 64      ; If V0==64, we finished drawing a complete line, so we
                          ; skip the jump to LOOP, as we have to update V1 too.
         
          JP  LOOP        ; We did not draw a complete line ? So we continue !
         
          LD  V0, 0       ; The first bitmap of each line is located 0, V1.
         
          ADD V1, 4       ; We update V1. The next line is located 4 pixels doan.
         
          SE  V1, 32      ; Have we drawn all the lines ? If yes, V1==32.
          JP  LOOP        ; No ? So we continue !
         
      FIN:    JP FIN      ; Infinite loop...
         
      RIGHT:              ; 4*4 bitmap of the left line
         
          DB $1.......
          DB $.1......
          DB $..1.....
          DB $...1....
         
      LEFT:               ; 4*4 bitmap of the right line
                          ; And YES, it is like that...
          DB $..1.....
          DB $.1......
          DB $1.......
          DB $...1....
       


занимающая 38 байт и в скомпилированном виде выглядящая так



должна в конечном итоге выполниться в нашем эмуляторе и вывести на экран примерно такую картинку:



С концом покончили, переходим к немного нудной, но необходимой теории.

Архитектура

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

CHIP-8 – это интерпретируемый язык программирования, созданный в середине 70-х годов для игровых приставок COSMAC VIP и Telmac 1800. Программы, написанные и скомпилированные для CHIP-8, выполняются на самих приставках в виртуальных машинах. Ну, по современной аналогии это что-то вроде Java байт-кода. Я же вообще советую забыть на время создания эмулятора о том, что это интерпретируемый язык, и считать, что мы эмулируем железную платформу – некий процессор со своим набором команд. Далее, когда я буду говорить “приставка”, я буду подразумевать CHIP-8.



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

Память

Приставка имеет 4Kb основной памяти (RAM). Память начинается со смещения 200h и заканчивается смещением FFFh соответственно. Почему память для программ начинается со смещения 200h? Все очень просто – первые 512 байт памяти в оригинальных приставках как раз занимает интерпретатор языка CHIP-8 в машинных кодах того процессора, на котором построена приставка.

Регистры

В CHIP-8 существует шестнадцать 8-битных регистров данных с именами V0… VF. Регистр VF отвечает за флаг переноса (carry flag) при операциях сложения/вычитания. Также в приставке имеется 16-битный адресный регистр I.

Стек

Стек используется для сохранения адреса возврата, когда завершается выполнение подпрограммы. У оригинальной версии приставки размер стека составляет 48 байт, что соответствует двенадцати уровням вложения подпрограмм. Поскольку мы не ограничены в ресурсах, мы будем использовать 16 уровней вложений. Так делает большинство CHIP-8 эмуляторов.

Таймеры

В приставке присутствуют два 8-битных таймера, они оба уменьшаются с частотой 60 Гц, пока не достигнут нуля.
Delay timer: Этот таймер используется для различных задержек в играх, его значение можно читать/изменять с помощью команд.
Sound timer: Когда значение таймера отлично от нуля, выводится пищащий звук.

Устройство ввода

Ввод осуществляется с помощью 16 клавиш. В оригинальной приставке клавиши имеют коды от 0h до Fh.Если мы эмулируем на компьютере, то удобнее всего использовать правую NumPad часть клавиатуры, ту, где находятся цифры 0-9 и NumLock. Клавиши '8', '4', '6', и '2' обычно используются для перемещения, хотя и не всегда так. Это зависит от игры.

Графика и звук

В нашей приставке разрешение экрана 64x32 пикселя, один цвет (монохром). Вывод реализован с помощью спрайтов, которые всегда имеют ширину 8 пикселей и могут иметь длину от 1 до 15 пикселей. Если при рисовании спрайт накладывается на другой спрайт, то в точке наложения цвет инвертируется, а регистр VF (carry flag) принимает значение 1. Иначе он принимает значение 0.

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

Команды

Наш процессор (CHIP-8 на самом деле) имеет ровно 35 команд, каждая команда всегда имеет длину два байта. Здесь таблицу команд не буду перепечатывать, она есть в википедии. Можно разобрать несколько примеров оттуда, например:
00E0 Clears the screen. – когда встретим в коде 00E0, просто очистим экран.
6XNN Sets VX to NN. – установить регистр VX в значение NN. Например, если встретили команду 635A, значит нужно в регистр V3 записать значение 5Ah.

Практика

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

Понятно, что первым делом при запуске эмулятора мы должны проинициализировать нашу виртуальную машину. То есть очистить память, стек, регистры и видеопамять. Как я уже писал выше, смещение, по которому мы будем загружать нашу эмулируемую программу равно 200h. До этого, то есть со смещения 000h до 1FFh, должен находиться оригинальный интерпретатор. В нем, помимо всего прочего, присутствует маленький шрифт, который начинается со смещения 000h и до 050h и занимает 80 байт. Его можно увидеть в исходных кодах моего эмулятора. Да, прошу прощения за свой французский Delphi, но программирую я на нем, не обессудьте. Для простоты я создал такую структуру:

Code
      Display : Array [0..64*32-1] of Byte; //video memory
        Memory : Array [0..4095] of Byte; //RAM memory
        Stack  : Array [0..15] of Word; //stack
        Registers : Array [0..15] of Byte; //registers
        rI  : Word = $200;      // I register
        SP  : Byte = 0;         // stack counter
        PC  : Word = $200;      // mem offset counter
        delay_timer : Byte = 255; // delay timer;
        sound_timer : Byte = 255; // sound timer;
   


Итак, в начале мы заполняем нулями все массивы, затем копируем шрифт (Font: array [1..80] of byte) в массив Memory начиная с нуля и инициализируем все значения:

Code
FillChar(Memory,4096,0); // очищаем основную память
Move(Font,Memory,80); // копируем в нее шрифт по смещению 000h
FillChar(Stack,16,0); // очищаем стек
FillChar(Registers,16,0); // сбрасываем регистры в ноль
     
rI  := $200;      // адресный регистр I на начало программы
PC  := $200;      // смещение массива
     
SP  := 0;         // счетчик стека
delay_timer := 0; // таймеры в нули
sound_timer := 0;
   


Теперь все подготовлено, можно прочитать в память эмулируемую программу по смещению 200h и браться за интерпретацию кодов. Здесь придется немножко вспомнить, кто такие биты, и как их извлекать из байтов и слов (word). Для простоты я создал процедуру ExecuteOpcode(opcode: word), в которую передается опкод из двух байт, интерпретируется и выполняется. Чтобы понять смысл, можно сверятся с таблицей команд из википедии: http://en.wikipedia.org/wiki/CHIP-8

Code
Procedure ExecuteOpcode(opcode :word);
Begin
      case (op_code and $F000) shr 12 of  // выделяем из опкода первые 4 бита
          $00: Begin // опкод начался с нуля
                    Case op_code and $00FF of
                          // Это у нас опкод 00E0 - очистка экрана
                          $E0:  Begin
                     //Делаем дела, то есть  тупо очищаем экран
                     exit;
                     End;
                          // А это - 00EE - выход из процедуры
                          $EE:  Begin
                     // Восстанавливаем из стека адрес, прыгаем на него
                     exit;
                     End;
                    End;
                    // А сюда попадем, если опкод начался с нуля, но не закончился ни E0, ни EE
                    // Поэтому либо трапаемся, либо выводим сообщение Invalid Opcode
                    exit;
               End; //конец проверка на нулевой опкод
          $01: Begin // первые четыре бита опкода равно 1 (опкод начался с единицы)
                     // Это JMP, jump. Прыгаем на нужный адрес
                    PC := op_code and $0FFF;
                    exit;
               End;
          $02: Begin // первые четыре бита опкода равны 2 (опкод начался с двойки)
                     // Вызываем подпрограмму.
                     // увеличиваем указатель стека
                     // заносим в стек текущий адрес
                     // и пыгаем на подпрограмму
               End;
         //
         // Так продолжается до опкода, который начинается с 7.
         //
     
          $08: Begin  // опкод начался с 8. Здесь нужно смотреть на 4 последних бита
                 case op_code and $000F of  // последние 4 бита опкода
                  // mov vx, vy
                  $00: Begin
                          // Занесем в регистр VX значение VY
                          exit;
                       End;
                  // or vx, vy
                  $01: Begin
                          // VX = VX or VY
                          exit;
                       End;
                  //
                  // так продолжается до 0E
                  //
     
                 End; // конец проверки последних 4 бит опкода
                 // сюда попадаем, если Invalid Opcode
                exit;
              End; // конец проверки, если опкод начался на 8
   


И так далее, думаю идея должна быть более-менее понятна. Во время написания интерпретатора можно пользоваться заглушками для каких-то команд. Теперь, когда мы реализуем основные команды процессора, останется сделать вывод на экран и реализовать устройство ввода. За вывод на экран отвечает команда DXYN. В регистре VX находится координата X, в регистре VY находится координата Y с которых мы должны начать рисовать спрайт. Адресный регистр I в это время указывает на битовый образ спрайта. Я не буду прилагать реализацию рисования графики, думаю тут не должно возникнуть сложностей, тем более всегда можно посмотреть в исходнике в конце данного поста. Так же и с клавиатурой.

Заключение

Конечно все детали реализации я не смог упомянуть в данной заметке. Цель — просто натолкнуть на мысль и показать разбор опкодов. Если кому-то интересно, можно посмотреть мою реализацию эмулятора на Delphi - http://rghost.ru/2262193, или найти другие реализации эмуляторов в интернете. Как модно говорить, тысячи их. Начиная от Visual Basic и заканчивая железными решениями.
Заранее прошу прощения за мой код, я не приводил его в порядок — вылил как есть. Основной интересный файл там — hchip.pas, в нем реализована вся эмуляция.

Так же существует неплохой англоговорящий форум EmuTalk, в котором специально выделена ветка посвященная эмуляции Chip-8 - http://www.emutalk.net/showthread.php?t=19894.

Страница, на которой можно скачать наверное один из самых лучших эмуляторов chip8 и игры под него - http://www.pong-story.com/chip8/

Да и вообще, по запросу в гугле «chip-8» можно найти все что нужно.

Что еще можно сделать? Можно немного модифицировать наш эмулятор для поддержки Super chip-8 инструкций и спрайтов. Да много еще чего можно.

Автор: http://tronix286.habrahabr.ru/
Прикрепления к сообщению: 5058865.png (7.5 Kb) · 6692999.png (7.4 Kb) · 3817950.png (9.0 Kb) · 8792026.jpg (72.4 Kb)


Сообщение отредактировал(а) Ang3L - Суббота, 2011-01-22, 18:07:23
 
AweTДата: Воскресенье, 2011-03-27, 21:24:27 | Сообщение # 2
Нет аватара у AweT
Пользователь
Странники
Сообщений: 15
Репутация: 0
Замечания: 0%
Не на форуме
Да намудрил не легче скачать с нета эмулятар
 
LeshaДата: Воскресенье, 2011-03-27, 21:36:16 | Сообщение # 3
Нет аватара у Lesha
Пользователь
Странники
Сообщений: 13
Репутация: 0
Замечания: 0%
Не на форуме
AweT, можно но он не праильно работать будет !! :p
 
MTCДата: Вторник, 2011-03-29, 17:28:37 | Сообщение # 4
Нет аватара у MTC
Новичок
Странники
Сообщений: 20
Репутация: 0
Замечания: 0%
Не на форуме
Спасибо , все получилось , я сам с себя удевился
 
  • Страница 1 из 1
  • 1
Поиск: