Premier pas dans l’émulation : chip8 en c

William Benakli – Bilal Seddiki – Alban Le Jeune

Histoire du chip8

Dans le cadre d’un projet d’étude en trinôme, nous avons été amené à développer un émulateur. On ne savait pas exactement vers quoi se tourner mais après quelques recherches nous sommes tombés sur CHIP-8. Le CHIP-8 est un langage de programmation et une machine virtuelle développée dans les années 1970. Créé par Joseph Weisbecker pour le COSMAC VIP, un micro-ordinateur, le CHIP-8 a été conçu pour simplifier le développement de jeux vidéo. Il comportait un ensemble d’instructions relativement simple et était portable sur différents systèmes. Bien que le COSMAC VIP n’ait jamais été largement adopté par les utilisateur. Ce n’est donc pas un système à proprement parlé. On parle donc plutôt d’interpréteur ici pour être exact, mais le terme d’émulateur n’est pas complètement erroné dans ce cas. En effet, tenter de le reproduire constitue généralement le premier pas pour quiconque désire explorer ce domaine.

Les principales sections du Chip8

La structure initiale

Pour la conception de départ du chip8, nous l’avons représenté par une structure de donnée Chip8 qui contient les principales sections et état d’un Chip8.

Représentation en code de la structure de donnée : Chip8

Dans la struct Chip8 il y’a de nombreux attributs, nous allons nous détaillé l’utilité des principaux attributs de la structure.

program_counter avec index_register

La stack

  • expliquer en quoi consiste la stack

L’affichage simple

  • expliquer ce que l’affichage uniquement avec le binaire basic avec le systeme de rafraichissement avec le delay

L’émulateur CHIP-8 (c’est pas vraiment un émulateur) doit inclure une police intégrée contenant des données de sprite pour les chiffres hexadécimaux. Chaque caractère de la police doit avoir une largeur de 4 pixels et une hauteur de 5 pixels. Ces sprites de police sont dessinés de la même manière que des sprites ordinaires. Il est obligatoire, de stocker les données de la police en mémoire, car les jeux utiliseront ces caractères comme des sprites classiques : ils définiront le registre d’index I sur l’emplacement mémoire du caractère avant de le dessiner. Une instruction spéciale permet de définir I sur l’adresse d’un caractère, offrant ainsi la possibilité de choisir son emplacement. Tout emplacement dans les premiers 512 octets (000–1FF) est approprié. Pour une raison quelconque, il est devenu courant de le placer entre 050 et 09F ; vous pouvez suivre cette convention si vous le souhaitez.

La mémoire: uint8_t memory[MEMORY_SIZE];

La mémoire à une taille de 4 ko. Le registre d’index, le compteur de programme et les entrées de la pile ont tous une longueur réelle de 16 bits. Toute la mémoire est RAM et doit être considérée comme inscriptible. Les programmes CHIP-8 que vous trouvez en ligne sous forme de fichiers binaires sont souvent appelés des “ROMs”, comme les fichiers de jeux pour les émulateurs de jeux vidéo, mais contrairement aux jeux sur cartouches de console, ils n’étaient pas réellement ROM (ce qui signifie “read-only memory”).

Les instructions vont s’exécuter dans la mémoire, et seront placés à une certaine position lors du chargement de la rom. Le compteur de programme va se positionner sur l’instruction courante et l’instruction sera traitée dans la boucle du code. Mais avant nous la transformons en structure de donnée nommé nibble.

Les instructions

Qu’est-ce qu’un nibble ?

Nous avons décidé d’utiliser une structure de donnée nommé nibble pour interpréter les instructions de la rom placées en mémoire.

Représentation en code de la structure de donnée : Nibble

Les instructions sont décodés en structure nibble. Cela simplifie grandement l’accès aux différentes instructions. Il est donc plus simple d’accéder aux valeurs pour chaque instruction. Nous avons fait cela à l’aide de notre fonction décode, qui décale les bits contenu dans le opcode (l’instruction lu) et on attribut ce décalage de bit à l’objet nibble.

Fonction decode() présente dans chip8.c

Voici un exemple pour cette instruction “1NNN”. Ici 1 NNN, le opcode = 1 et le champs .nnn réprésente directement notre instruction. Donc il est plus facile de simplement décoder ça en object nibble et son utilisation nous simplifiera tout le travail par la suite.

Liste des instructions

Il existe une liste non exhaustives d’instructions. Disponible ici, sur le Wikipédia de CHIP8. Certaines instructions sont plus utilisés que d’autres. Elle peuvent se répertorier en groupe d’instructions.

Groupe d’instructions :

  • A définir

Nous allons nous concentrés, sur les instructions les plus utilisés.

OpcodeTypeExplication des instructions
Sources: https://en.wikipedia.org/wiki/CHIP-8 – https://tobiasvl.github.io/blog/write-a-chip-8-emulator/#0nnn-execute-machine-language-routine

Il existe tout de même des instructions bien plus utilisés dans les ROM’S.

La boucle principale

La boucle principale est au centre du programme chip8.c son principe est que chaque instruction lu dans la rom soit traité. L’idée peut se représenter avec un schéma :

Chaque instruction lu sera transmise à la boucle, et en fonction de l’instruction. Des actions seront effectués sur le Chip8.

Entrons plus en détail sur la conception de la boucle.

Dans un premier temps, lorsque le programme se lance. Le fichier ROM se charge dans la mémoire, ou les instructions sont chargés (vu plutôt). Pour cela nous avons conçu la fonction load_rom().

Capture d’écran de la fonction load_rom. Cette fonction permet de charger la rom

Avec cette fonction nous avons chargé en mémoire dans la structure chip8, toutes les instructions présente dans le fichier rom.

Le keypad, l’interface homme – chip8

Nous avons à présent un jeu qui tourne correctement et avec une rendu visuelle. Il faut donc avoir des interactions entre le chip8 et l’utilisateur. Pour cela il faut faire ce qu’on appelle du mapping de touche. En effet chaque action effectué sur le clavier est intercepté lorsque le chip8 est exécuté …

expliquer en large et en détail comment le mapping des touches sont faites dans le code et sur chip8

SDL

Depuis le début on reste flou quant à l’affichage et au rendu du système. Il s’avère que nous avons utilisé la libraire SDL. Comment avons-nous fait ? Eh bien, cela n’a pas été simple, puisque nous avons dû, dans un premier temps, convertir l’affichage d’un tableau de 5*16 en pixel.

à définir plus proprement

On obtient un rendu graphique plus détaillé et à l’échelle.

Désassembleur, pour y voir plus clair

Du binaire à l’assembleur

Lorsqu’on observe les roms trouvées sur internet. En ouvrant la rom tetris.rom par exemple. On remarque qu’on ne comprend pas les instructions de la rom, on obtient du texte illisible. Il s’agit d’un fichier binaire.

Capture d’écran du fichier tetris.rom

Après l’exécution de notre programme désassembleur sur la rom on obtient un autre fichier nommé : tetris.asm, avec des instructions lisibles.

Capture d’écran du fichier tetris.asm

Les instructions dans la rom

Les instructions sont écrit comme en assembleur et dans un certain ordre. Ce qui facilite la compréhension. Allons plus loin dans l’explication.

On remarque clairement 2 séparation dans ces instructions:

Capture d’écran des instructions après l’exécution du désassembleur

En rouge c’est l’index où se trouve l’instruction dans le fichier. Il commence à la position 200. En bleu c’est l’instruction avec (ou sans) ses arguments. L’instruction est limité à 16 octets.

Les cas d’erreurs

Il peut arriver à certain moment que les instructions ne soient pas à proprement traduite par le désassembleur car non prise en compte, dès lors on obtient une ligne d’erreur “Unknown opcode : code”.

Nous venons de voir ce que le désassembleur produit sur les fichiers roms. Regardons ensemble son fonctionnement.

Le fonctionnement de notre désassembleur

Le fonctionnement est très proche de la boucle principale du chip8. On retrouve le même schéma d’exécution.

Chaque instruction est lue et placée dans un objet nibble avec les fonctions fetch() et decode(). (même principe que la boucle principale)

Une fois l’objet nibble formé, on appelle la fonction log_op avec la structure nibble en paramètre.

Dans la fonction log_op, on lit la structure nibble. On la détermine et en fonction des valeurs présentent dans le nibble on écrit la traduction de l’instruction avec son index dans la rom dans le buffer.

Capture d’écran de la fonction log_op qui écrit dans le buffer la valeur comprise dans la structure de donnée nibble.

Enfin, après la lecture de toutes les instructions, on écrit les valeurs stocké dans le buffer sur un fichier. On obtient un fichier avec des instructions lisibles.

Conclusion sur Chip8

Le Chip8 est une réelle porte d’entrée au monde de l’émulation, même s’il ne s’agit pas l’un d’un vrai émulateur à proprement parlé, actuellement vous avez de bonnes pistes sur comment fonctionne un émulateur dans son ensemble. Cela va vous permettre, qui sait, d’aller plus loin dans la conception d’émulateur.

Pour aller plus loin

Maintenant, vous pouvez aller plus loin pour vous amuser. Il est possible, par exemple, de rendre votre chip8 compatible avec Retro Arch, comme vu dans l’article de mon binôme Bilal Seddiki, et ainsi pouvoir le déployer sur plusieurs machines: téléphone, borne d’arcade etc. Ou encore faire la version couleur du Chip8. Toutes ces idées sont à votre portées.

Nos sources

http://www.mac-emu.net/dossiers/un-peu-d-histoire/article/qu-est-ce-que-l-emulation
https://www.grospixels.com/site/emustory.php
https://www.giantbomb.com/emulation/3015-1587/
https://stackoverflow.com/questions/1584617/simulator-or-emulator-what-is-the-difference
https://www.tomshardware.fr/lemulation-comment-pourquoi/
https://softwarerecs.stackexchange.com/questions/56641/software-to-fully-simulate-machine-language-instructions-8051
https://www.dz-techs.com/fr/virtualization-vs-emulation/
http://www.ordinateur.cc/syst%C3%A8mes/Comp%C3%A9tences-informatiques-de-base/200886.html
https://fr.wikipedia.org/wiki/CHIP-8
https://tobiasvl.github.io/blog/write-a-chip-8-emulator/#make-your-own-chip-8-game

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.