![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
Ouvrir sur le forum | Recherche | Messages du jour | Marquer les forums comme lus |
Projets Projets de développement amateur sur d'autres supports |
Publicité |
![]() |
|
Outils de la discussion | Modes d'affichage |
![]() |
#1 |
Super Modérateur
Date d'inscription: 10/11/2005
Localisation: Un pays avec beaucoup de banques
Messages: 3 229
|
![]() Présentation : VoilÃ* une idée de projet complètement inutile qui m'a traversé l'esprit: faire une console de jeu virtuelle. Ca m'est venu au cours de compilation, au lieu de générer du code pour MIPS, autant se faire une machine virtuelle et y ajouter un petit hardware dédié. Galerie d'image : ![]() Historique : 12.06.2009: sortie initiale Comment utiliser : - Autres informations : Voici donc la PatrickBoy, croisement entre la Game Boy et les jeux inutiles Super Patrick (je lui avais même fait un site dans un autre cours: http://brunni.dev-fr.org/tmp/cat). Derrière cette présentation burlesque se cache en fait quelque chose d'assez sérieux: un projet purement software visant la définition d'un CPU "minimal" permettant d'avoir de bonnes performances malgré une fréquence relativement faible (4 MHz étaient prévus initialement, mais jusqu'Ã* 16 sont possibles comme c'est la fréquence de la carte mère). CPU auquel on rattache un hardware permettant de faire du graphisme, du son et autres. Il faudra alors programmer un assembleur, un émulateur et éventuellement un compilateur pour un langage de haut niveau. Evidemment toutes les specs devaient être de l'ordre du faisable, donc une réflexion sur comment on pourrait l'implémenter Ã* été faite Ã* chaque fois, histoire de rester réaliste. Bref c'est inutile mais c'est pédagogique, au moins ça. Peut être que mon compte rendu pourra intéresser des gens, c'est pour ça que j'ai décidé de le poster ici. Commençons donc avec le CPU: il s'agit d'un RISC 16 bits. C'est Ã* dire que les instructions, les registres et tout sont sur 16 bits. Il s'agit d'une architecture de Harvard, c'est Ã* dire que le code et les données sont séparées. Pourquoi? si l'adressage est sur 16 bits on est limité au maximum Ã* 64 ko de mémoire: RAM, ROM, VRAM tout compris. C'est bien peu... Une solution est alors de faire de la pagination (par exemple laisser 16k pour la RAM, et préciser quels 16k on adresse). La deuxième est de compacter tout ça: comme les instructions sont sur 16 bits (2 octets) on sait que toute instruction est exécutée Ã* une adresse paire, on peut donc éliminer le dernier bit (toujours nul). Si en plus on oblige l'exécution du code en ROM seulement, on se retrouve alors avec 128 ko exécutables. Le CPU dispose de 30 instructions au total, en comptant les différents modes: par exemple add r0, r1 (registre-registre) n'est pas la même chose que add r0, 7 (registre-littérale). L'assembleur était quelque chose d'assez intéressant Ã* faire, mais loin d'être aussi facile qu'on le pense, comme on le verra plus tard. Donc la syntaxe ça c'est chacun qui choisit. Par exemple une routine qui remplit la palette de nuances de rouge: Code:
#include "patlib.h" $routine mov r0, PALETTE ; destination mov r1, 0 ; couleur :boucle st r1, [r0+] ; stockage dans la palette et incrémentation du pointeur add r1, 1 ; prochaine couleur (+1) bne r1, 32, :boucle ; on va jusqu'Ã* 32 (bne = branch if not equal) return Dans le code ci-dessus si vous regardez le document vous remarquez qu'il n'existe pas d'instruction pour effectuer directement bne registre, constante 6 bits, label. Le plus proche est b[condition] registre, registre, offset. L'assembleur va alors devoir modifier l'instruction et générer le code suivant (at étant un registre réservé Ã* l'assembleur): Code:
mov at, 32 bne r1, at, -4 Code:
b :label ; saut inconditionel 128 instructions plus loin space 256 ; 256 instructions :label Code:
mov at, 256 b at Code:
mov at, 0 movhi at, 1 ; 0x0100 b at Code:
add r0, 1 pourrait devenir: mov at, 1 add r0, at Maintenant le problème qui se pose, c'est quand générer de "gros sauts" et quand générer de petits quand on ne sait pas encore où va se placer un label? (i.e. on ne l'a pas encore rencontré) Code:
petit saut: b +35 grand saut: mov at, 35 movhi at, 0 b at Autre truc intéressant: la résolution d'expression mathématique. On peut par exemple écrire: Code:
mov r1, (PALETTE + 10) ; PALETTE étant une constante mov r0, 31 st r0, [r1] Donc nous y voilÃ*, l'assembleur supporte aussi les macros et autres joyeusetés qui peuvent être définies Ã* la C. Code:
#define add(i, j) ((i) + (j)) mov r0, add(1, 2) ; r0 = 3 Le préprocesseur analyse donc l'entrée et la convertit en "mots" (en fait même une virgule est un mot, au sens que c'est un élément syntaxique). On a en sortie ce qu'on appelle des jetons: c'est Ã* dire un texte lu associé Ã* un élément syntaxique. A chacun de décider ce qui forme les éléments syntaxiques de son langage. Si je prends par exemple une instruction, pour moi cela représente: Code:
mov r0, 10 alors on a: <jeton type="identificateur" texte="mov"> <jeton type="espace" texte=" "> <jeton type="identificateur" texte="r0"> <jeton type="virgule" texte=","> <jeton type="espace" texte=" "> <jeton type="nombre" texte="10"> On peut ensuite créer un analyseur simple qui prend ces jetons en entrée et détermine quoi faire avec. Par exemple on sait qu'en début de ligne on doit avoir un nom d'instruction. On va ensuite effectuer la procédure correspondant Ã* l'instruction qu'on a rencontrée. J'ai fait une fonction prochain qui me permet de vérifier que le prochain jeton est bien d'un type (pour la décision) et une fonction lit retournant le prochain jeton et avançant le curseur (de sorte qu'Ã* la prochaine lecture on aura le prochain). Prenons mov, elle est relativement complète: Code PHP:
Donc voilÃ*, ce préprocesseur, on peut alors lui ajouter deux trois trucs sympa, comme par exemple #include, qui va ouvrir un autre fichier et poursuivre la lecture sur celui-ci. Il ajoutera les jetons qu'il lit sur la même liste en sortie. On peut aussi permettre la définition de macros Ã* la C. Reprenons l'exemple: Code:
#define add(i, j) ((i) + (j)) mov r0, add(1, 2) ; r0 = 3 Code PHP:
En effet, notons qu'on peut définir plusieurs macros Ã* la suite comme ceci, impossible en C. Code:
#define x=r0, y=r1 mov x, 0 #undef x, y Une fois une macro définie, le préprocesseur continue d'analyser le texte et de le convertir en jetons, mais une fois qu'il trouve un identificateur (un mot texte) il vérifie s'il ne fait pas partie des macros. Si c'est le cas il va vérifier s'il est suivi d'une parenthèse et commencer Ã* rassembler les arguments, qu'il passera Ã* la macro. Ensuite le préprocesseur va voir son entrée transférée sur le texte de la macro en question. LÃ* aussi lorsqu'il rencontre un identificateur, il va vérifier récursivement s'il s'agit d'une macro, ou cette fois s'il s'agit d'un nom de paramètre. Il le remplacera alors par le texte lu lors de l'appel. On a vu plus tôt que certaines instructions pouvaient paraître ambiguës. En effet si j'écris: Code:
mov r0, PALETTE Code:
#define r0=10 mov r1, r0 Code:
mov r0, pal Ensuite reste Ã* proposer de petites aides au programmeur. Par exemple vous avez vu plus haut l'instruction return. En fait il s'agit d'une pseudo instruction. Un appel réalise en fait ceci, de façon très similaire Ã* l'ARM: il sauve la valeur courante du pc (registre spécial r15, pointeur d'instruction en train d'être exécutée, qui lors de l'exécution d'une instruction i, pointe sur i+1, c'est Ã* dire la prochaine) dans un registre spécial appelé lr (r14) et met dans pc l'adresse de l'instruction appelée de sorte que la prochaine sur la liste sera celle-ci. Au retour il suffit alors de mettre dans pc, prochaine instruction Ã* exécuter, la valeur de lr sauvée pour retourner juste Ã* l'endroit où l'appel s'est fait. Si une fonction en appelle une autre, alors elle devra prendre soin de sauvegarder la valeur de lr car cet appel le modifiera. Exemple: Code:
$main call :fct1 b -1 ; boucle infinie, en effet si cette instruction est Ã* l'adresse 0001, lors de l'exécution pc pointe sur 0002, donc le brancher Ã* 2 - 1 = 1 lui refera exécuter éternellemetn cette même instruction :fct1 push lr call :fct2 pop lr return :fct2 return Code:
push r0 pop r0 se transforment respectivement en: st r0, [-sp] ld r0, [sp+] Bon alors maintenant le CPU il est plus ou moins bon ![]() Concernant la RAM, on laissera la gestion 100% libre Ã* l'utilisateur. Un truc tout de même: le BIOS de ma console copie une partie de la cartouche dans les 16 premiers ko de la RAM. Cela sert pour l'initialisation, de sorte qu'on puisse écrire ceci pour se réserver des variables pré-initialisées: Code:
#section ram :var1 space 2 ; 2 octets :var2 data 12 ; 2 octets, vaut 12 de base :var3 data8 12 ; 1 octet, vaut 12 de base :var4 string "Hello world" ; liste d'octets, automatiquement complétés d'un octet nul #section code $main mov r1, :var2 ld r0, [r1] add r0, 1 ; 13 st r0, [r1] mov r0, 0 st8 r0, :var3 ; décomposé en mov at, :var3 et st8 r0, [at] Pour l'interprétation de la VRAM, c'est complètement libre. J'ai décidé d'un GPU avec un certain modèle, et j'ai donné donc une certaine signification Ã* ce qui s'y trouvait: où on met la map, les tiles, quel est leur format, etc. Perso j'ai fait un GPU pouvant afficher 2 plans et jusqu'Ã* 128 sprites avec des effets d'addition et de soustraction. Les 2 plans sont des maps comme d'habitude et on retrouve des registres de configuration situés dans la zone IO (adresses f800 et plus), permettant par exemple de définir la valeur de scrolling, configurer le mode graphique (bitmap 4, 8 bits ou texte), quelle map utiliser, etc. pour plus d'informations, voir le document associé. Je vais abréger le reste car c'est assez simple si on connaît un peu le dév sur GBA par exemple. Passons maintenant Ã* l'émulateur. En fait cette étape a été étonnamment simple. J'ai tout d'abord créé une application Direct3D (parce que je voulais absolument la VSync et j'ai jamais compris comment l'avoir Ã* coup sûr en OpenGL. Mais il faudrait changer ça parce que le SDK est vraiment trop lourd et Windows only). Ensuite comme le hardware de notre console est constitué de plusieurs éléments qui travaillent indépendamment les uns les autres, il est difficile de le représenter par un logiciel. Quelque chose qui vient en tête toutefois est de décomposer l'exécution en petites unités de temps et exécuter "une partie" de ce que chacun doit faire Ã* chaque unité de temps. Cette unité de temps dans mon cas est le cycle d'horloge de l'oscillateur principal, celui qui est sur la carte mère. Il est cadencé Ã* 16 MHz, et il déclenche tous les événements pouvant arriver aux éléments matériels. Il constitue donc une source idéale. J'ai donc défini les timings: ce que fait chaque élément, et Ã* quel fréquence. Ensuite il faut simplement Ã* chaque cycle d'horloge effectuer les actions du matériel qui ont lieu, selon les spécifications théoriques: * exécuter une instruction du CPU et attendre le temps qu'il l'exécute (faire autre chose pendant ce temps) * générer un pixel de l'affichage tous les 4 cycles * gérer les fin de ligne, fin de frame et générer les interruptions appropriées, mettre Ã* jour les registres (DISPSTAT, n° de ligne en cours) et les waitstates en conséquence (la VRAM est plus lente d'accès si l'affichage est en cours). * générer une sample audio tous les 381 cycles * continuer le DMA s'il est en cours et qu'il a terminé * décrémenter le timer et générer une interruption s'il y a lieu * etc. Ce modèle est louuuuuurd! En fait on va consommer énormément de CPU si on fait vraiment ça Ã* tous les coups. On peut toutefois optimiser en ne décrémentant par exemple pas le registre Ã* tous les ticks d'horloge, mais par exemple calculer lorsque le programme utilisateur (le jeu en cours d'émulation quoi) le demande, le temps qui s'est écoulé depuis le dernier événement le concernant et connaître ainsi Ã* quelle valeur il devrait être. Pareil pour l'affichage, en principe pour être précis on devrait générer pixel par pixel, mais de toute façon ce qui se passe au milieu des lignes est assez peu déterministe et un jeu ne devrait pas compter lÃ*-dessus. On dessinera alors l'affichage ligne entière par ligne. Cela déchargera énormément le CPU, et on pourra alors mettre en pause le GPU durant une période plus grande. En effet lorsqu'un élément, par exemple le CPU, exécute quelque chose, il va ensuite se mettre en "pause", c'est-Ã*-dire qu'il va attendre d'être réveillé lorsqu'il aura terminé. Entre temps il ne se passera rien le concernant, mais les autres composants eux vont pouvoir continuer Ã* bouger. Pour le CPU s'il exécute une instruction qui lui a pris 2 cycles et qu'il était Ã* 4 MHz (diviseur de fréquence par 4 comparé aux 16 MHz) alors il se mettra en pause pour 8 cycles. A chaque ligne le contrôleur vidéo se mettra en pause le temps (théorique) de dessiner sa ligne, soit 1232 cycles si ma mémoire est bonne. Toutes les pièces s'emboîtent! Bon allez c'est l'heure de bouffer je rédigerai la suite une autre fois si ça intéresse des gens! Je vais aussi terminer cet ému si j'ai le temps et rendre les choses plus propres. En attendant voici un lien de téléchargement. Vous pouvez compiler une petite ROM et l'exécuter avec le build.bat fourni. Le CPU et la RAM ont vraiment des clocks de merde, genre 8 kHz. C'est ultra lent, on voit les choses s'écrire Ã* l'écran. Vous pouvez changer ça dans CPU.cpp (unsigned cpu_cycle_factor = 2048, devrait être 4 pour 4 MHz). Pour l'instant le contrôleur (clavier), les timers et les fenêtres (dont les specs sont sujettes Ã* changement) ne sont pas implémentées. Screenshot: Document: http://brunni.dev-fr.org/tmp/cat/oth...umentation.pdf PatrickBoy tout complet: http://brunni.dev-fr.org/tmp/cat/oth.../PatrickBoy.7z
__________________
[10.12.2018] PatrickBoy: codez vos jeux avec la puissance d'une borne d'arcade 16 bits! [21.01.2010] Emu Game Boy et GUI pour la coloration de jeux GB Partagez vos meilleures musiques de jeu vidéo! ![]() ![]() ![]() Dernière modification par Brunni ; 10/10/2009 à 10h36. |
![]() |
![]() |
Publicité |
![]() |
#2 |
Graphiste / Modérateur
|
![]() Ce qui est cool avec Brunni c'est qu'il fait jamais des trucs pointus, que des merdouilles en 3 lignes de code. Quelqu'un peut m'expliquer le concept de "console de jeu virtuelle" ?
![]() |
![]() |
![]() |
![]() |
#3 |
Maître Chinpoko-extra-mon
|
![]() C'est comme un émulateur, sauf que la machine n'existait pas avant. Enfin, je crois... J'ai pas tout capté.
![]()
__________________
"Un pour l'argent, deux pour le spectacle et trois pour le cailloux" un putain d'énergumène
|
![]() |
![]() |
![]() |
#4 |
Super Modérateur
Date d'inscription: 10/11/2005
Localisation: Un pays avec beaucoup de banques
Messages: 3 229
|
![]() Lol j'ai pas dû être clair
![]() C'est une console de jeu inventée. Le but c'est de voir Ã* toutes étapes théoriques (sans vraiment faire le matériel parce que je m'y connais pas) ce qu'il y a Ã* faire: du design de la console, ce que fait le hard, le CPU, puis les outils genre un assembleur pour pouvoir écrire du code pour la machine, un émulateur pour pouvoir tester (la console n'existe pas en vrai donc faut bien un moyen), mais aussi d'autres outils tels que GBA Graphics qui existait déjÃ* avant, un tracker pour composer de la musique, un compilateur pour un langage plus évolué tel que le C, etc. Bref on voit que la liste est longue, c'est d'ailleurs pour ça que je n'ai pas été jusqu'au bout. En fait c'est une intro pour pas mal de choses dans le bas niveau, et ça peut intéresser les gens qui n'ont pas le temps de s'y mettre, au moins voir un peu comment c'est fait. Ca n'a pas d'intérêt réel en tant que tel, par contre je pense faire un vrai ému pour une vraie console depuis 0 ensuite, genre la Game Boy. Les bases sont les mêmes excepté que la console existe déjÃ* donc y a pas la réflexion préliminaire Ã* faire, par contre pas mal de docs Ã* se farcir ![]() |
![]() |
![]() |
![]() |
#5 |
Membre confirmée
Date d'inscription: 09/08/2007
Localisation: Belfort :'(
Messages: 573
|
![]() J'avoue que l'idée d'un système entièrement fictif et virtuel ne m'a jamais traversé l'esprit !
Vraiment très intéressant ![]() Question : pourquoi que 30 opcodes ? Comme c'est toi qui définit ton propre CPU, pourquoi ne pas y ajouter quelques opcodes bien pratiques ? Y'en a un par exemple utilisé dans les DSP, qui effectue une addition ainsi qu'une multiplication (je ne me rappelle plus de son nom...). |
![]() |
![]() |
![]() |
#6 |
Super Modérateur
Date d'inscription: 10/11/2005
Localisation: Un pays avec beaucoup de banques
Messages: 3 229
|
![]() Le but c'était d'être minimal, dans l'esprit RISC. Si j'avais vraiment voulu coder 40000 opcodes je me serais attelé Ã* émuler un 68k par exemple
![]() En fait si tu regardes le jeu d'instruction y a pas besoin d'aller si loin, déjÃ* rien que faire un masque c'est compliqué: Code:
; branche Ã* bitset si r0 & 0x100 (bit 8) sans modifier r0 mov r2, r0 ; copie de r0 mov r1, 1 shl r1, 8 ; r1 = 1 << 8 and r2, r1 bnz r2, :bitset Dans d'autres CPUs on a inclus des instructions bittest par exemple pour éviter justement ces problèmes. Mais c'est lÃ* qu'on voit la magie de ce que sait faire l'ARM, où ça donnerait: Code:
ands r2, r0, #0x100 ; codage constante 8 bits + décalage 5 bits (imm8m) bne bitset |
![]() |
![]() |
![]() |
#7 |
Graphiste / Modérateur
|
![]() Ok, merci pour l'explication Brunni
![]() |
![]() |
![]() |
![]() |
#8 |
Membre confirmé
Date d'inscription: 10/11/2005
Messages: 830
|
![]() cool !
C'est sûr que ça change radicalement de l'opcode arm32, mais ça a son charme je trouve, en tout cas c'est du bon boulot que tu as fait la. ![]() Vivement que ça soit en phase beta ![]() je me serai bien laissé tenter par un joli voxel Ã* l'ancienne mais la vitesse de l'emu m'a ramené Ã* des considérations plus pragmatiques. En attendant voici un sprite Ã* la con qui rebondit sur les bords, asm rulez ![]() C'est vraiment très lent. code:
Spoiler
|
![]() |
![]() |
![]() |
#9 |
Super Modérateur
Date d'inscription: 10/11/2005
Localisation: Un pays avec beaucoup de banques
Messages: 3 229
|
![]() Sympa, merci d'avoir testé
![]() Nan mais je de tte vais uploader une autre version avec au moins le CPU Ã* 4 MHz plutôt que 8 kHz, parce que lÃ* c'est pour le test on peut vraiment rien faire Ã* cette fréquence lol ![]() A 4 MHz par contre on ne voit rien, faudra utiliser le HALT (attente d'une interruption)... Et juste un truc: le mode bitmap 8 bits a surtout été prévu Ã* des fins de débogage, pas vraiment pour être utilisé étant donné qu'il n'y a pas de double buffering (pas assez de VRAM)... donc tu risques de t'embêter pour ton voxel, malgré la grosse période de VBLANK.
__________________
[10.12.2018] PatrickBoy: codez vos jeux avec la puissance d'une borne d'arcade 16 bits! [21.01.2010] Emu Game Boy et GUI pour la coloration de jeux GB Partagez vos meilleures musiques de jeu vidéo! ![]() ![]() ![]() Dernière modification par Brunni ; 15/06/2009 à 12h21. |
![]() |
![]() |
![]() |
#10 |
Membre confirmé
Date d'inscription: 23/07/2007
Messages: 134
|
![]() Salut.
C'est vraiment pas mal ! J'avoue que je comprends pas grand chose... voir rien du tout ![]() ![]() Pour le CPU, tu as utilisé un existant, ou bien tu en as "inventé" un ? Pourquoi ne pas se baser sur un PIC, AVR, ou tout autre microcontroleur qui pourrait servir de CPU ? Ça pourrait éventuellement permettre de faire une partie hardware adapté ![]() Perso, je ne connais pas trop l'hardware non plus, mais je pense me lancer tranquillement. Donc si un jour tu veux adapter un hardware, ça serait sympa comme projet ![]() A bientôt et bonne chance pour la continuation.
__________________
Dernière modification par CrazyLapinou ; 23/06/2009 à 23h45. |
![]() |
![]() |
![]() |
Liens sociaux |
Tags |
assembler asm tutorial |
Publicité |
Utilisateurs regardant la discussion actuelle : 1 (0 membre(s) et 1 invité(s)) | |
Outils de la discussion | |
Modes d'affichage | |
|
|
![]() |
||||
Discussion | Auteur | Forum | Réponses | Dernier message |
[PROJET] A Touch of War | dem1980 | [NDS] Divers | 12 | 10/04/2006 17h05 |
[projet] Recherche codeur pour projet RPG | thoduv | [DEV] Divers | 2 | 03/12/2005 18h46 |