Ce que j'ai appris sur la machine d'arcade Bomb Jack en train de créer son émulateur
J'ai récemment écrit un petit émulateur pour une machine Bomb Jack, principalement pour comprendre en quoi ces premières machines d'arcade 8 bits différaient par leur conception des ordinateurs domestiques 8 bits.
Comme je l'ai appris beaucoup plus tard, une réunion dans une foire d'été dans ma ville natale avec des machines d'arcade comme Bomb Jack a été l'un de ces moments qui ont changé mon destin. Un jour d'été normal, après avoir dépensé toute ma réserve de pièces de monnaie sur des machines d'arcade, je suis rentré chez moi et ma tête était remplie de fleurs et d'effets sonores. J'ai essayé de comprendre comment ces jeux fonctionnaient. Et puis jusqu'à la fin de l'année, j'ai passé tout mon temps après l'école à créer des copies plutôt fanées de ces jeux d'arcade sur mon ordinateur personnel. J'étais comme un fan du culte du cargo des îles de l'océan Pacifique, qui voulait créer une station de radio militaire américaine à partir de bâtons.
Au début, j'ai pensé à l'idée de créer un émulateur
Pengo , parce que mon cerveau d'adolescent était beaucoup plus impressionné par ce jeu que Bomb Jack (au fait, voici ma
version culte de Pengo ). Mais l'équipement d'arcade Pengo nécessiterait la création de nouveaux émulateurs de puces pour l'audio et la vidéo, et pour Bomb Jack, il y avait suffisamment de pièces que j'avais déjà (Z80 en tant que CPU et AY-3-8910 pour le son), donc j'ai été le premier à prendre Bomb Jack.
De plus, Bomb Jack a été une excellente occasion d'ajouter enfin le support NMI (interruption non masquable) à mon émulateur Z80. Aucune des machines basées sur Z80 que j'ai émulées auparavant n'utilisait NMI, et donc il n'y avait pas grand intérêt à recréer cette fonction - je ne pouvais toujours pas vérifier son fonctionnement.
Si vous ne savez pas ce qu'est Bomb Jack, alors ce jeu ressemblait à ça (je ne sais pas si j'ai choisi le bon rapport hauteur / largeur):
La version de l'émulateur sur WebAssembly peut être trouvée ici:
https://floooh.imtqy.com/tiny8bit/bombjack.htmlUne fois la procédure de chargement terminée et le tableau des meilleurs scores affiché, appuyez sur
1 pour déposer une pièce, puis sur
Entrée (ou sur toute autre touche à l'exception des flèches et de la barre d'espace) pour démarrer le jeu.
Dans le jeu, utilisez les
touches fléchées pour changer de direction et la
barre d'
espace pour sauter. En l'air, appuyez sur la
barre d'
espace pour ralentir la chute.
Le code source est ici:
https://github.com/floooh/chips-test/blob/master/examples/sokol/bombjack.cIl utilise
des en-têtes de puce pour fournir une émulation des Z80 et AY-3-8910, ainsi que des en-
têtes sokol comme un wrapper multiplateforme (pour entrer dans l'application, le rendu, l'entrée et le son).
Étape 1: recherche
"Recherche" est un mot trop gros: je viens de cliquer sur "Spécifications matérielles de Bombjack arcade" de Google.
Comparé aux ordinateurs domestiques populaires des années 80 (ou même aux mystérieux ordinateurs d'Europe de l'Est, qui ont souvent encore des communautés actives), il y a très peu d'informations sur Bomb Jack sur Internet.
J'ai trouvé deux informations très importantes: le
schéma électrique de la machine et, bien sûr, le
code source de l'émulateur MAME .
Il y a aussi un projet qui implémente
Bomb Jack sur FGPA , à partir des sources VHDL dont j'ai réussi à trouver des détails qui ne sont pas dans le schéma du circuit.
Comprendre le code source de MAME serait délicat, car les émulateurs de machines d'arcade ne sont généralement qu'un tas de macros décrivant comment les différents équipements interagissent, mais il
n'y a pas beaucoup de
code source .
Néanmoins, les macro descriptions de l'équipement, et surtout les commentaires, se sont néanmoins révélés très utiles pour comprendre le fonctionnement du matériel, et là où ils sont devenus trop cryptiques (par exemple, la
partie sur le décodage vidéo ), la méthode d'essai et d'erreur était suffisante, ainsi que étude détaillée du concept.
Présentation du matériel
La chose la plus intéressante à propos du matériel Bomb Jack est qu'il s'agit en fait de
deux ordinateurs connectés l'un à l'autre par un ruban électrique: il y a une
carte principale avec un processeur Z80 et un équipement de décodage vidéo et une
carte son séparée avec son propre processeur Z80 et trois (oui, trois!) puces sonores AY-3-8910.
L'équipement de décodage vidéo n'est pas implémenté en tant que circuit intégré - c'est juste beaucoup de petites puces à usage général (leur circuit prend 6 pages sur 10 du schéma de circuit de l'appareil). Lors de la création d'un émulateur, j'ai décidé d'aller un peu plus loin: au lieu d'émuler des parties individuelles de l'équipement de décodage vidéo, j'ai émulé uniquement son comportement, créant la sortie correspondante à partir des données d'entrée et ne me souciant pas vraiment du fonctionnement de l'équipement lui-même au milieu.
Une telle solution simplifiée est tout à fait appropriée pour une machine d'arcade distincte, conçue pour exécuter un seul programme. Si le jeu démarre et fonctionne correctement, l'émulation peut être considérée comme "assez bonne".
De plus, cette approche simplifiée est une différence importante par rapport à l'émulation de la plupart des ordinateurs personnels: certains jeux nécessitent une émulation plus précise que d'autres, par exemple, des machines telles que C64 ou Amstrad CPC nécessitent une émulation très précise jusqu'à des cycles d'horloge, de sorte que les systèmes vidéo de certains jeux et graphiques les démos fonctionnaient correctement.
Cela signifie également que mes émulateurs de processeur et de puce audio prêts à l'emploi sont en fait superflus pour Bomb Jack, par exemple, travailler avec des processeurs Z80 avec la mise en œuvre de la fractionnalité du cycle machine est une exagération, une fragmentation plus simple et plus rapide au niveau de l'instruction suffirait.
Carte principale
Habituellement, la première chose que j'essaie de découvrir lors de l'écriture d'un nouvel émulateur est le schéma d'allocation de mémoire (où sont les zones de ROM et de RAM, de mémoire vidéo et d'adresses spéciales ou de ports d'entrée / sortie).
Il n'y a qu'une seule puce «intéressante» sur la carte principale de Bomb Jack - le CPU Z80 fonctionnant à 4 MHz. Tout l'espace restant sur la carte principale est occupé par un équipement de décodage vidéo (à l'exception d'une paire de puces RAM et ROM).
L'espace d'adressage 16 bits est le suivant:
- 0000..7FFF : 32 Ko de ROM
- 8000..8FFF : 4 Ko de RAM à usage général
- 9000..93FF : 1 Ko de mémoire vidéo
- 9400..97FF : 1 Ko de RAM couleur
- 9820..987F : 96 octets de RAM sprite
- 9C00..9CFF : 256 octets de palette de couleurs RAM
- 9E00, B000..B005, B800 : ports d'entrée-sortie
- C000..DFFF : ROM 8 Ko
La zone du port d'E / S est la suivante. Certains ports sont en écriture seule, certains sont en lecture seule et certains ont des fonctions différentes lors de la lecture et de l'écriture:
- 9E00 : écriture: numéro de l'image d'arrière-plan actuelle, lecture: -
- B000 : lecture: état du joystick du lecteur 1, écriture: activation / désactivation du masque NMI
- B001 : lire: état du joystick du joueur 2, écrire: -
- B002 : lire: pièces et boutons Démarrer, écrire: -
- B003 : lire: chien de garde CPU, écrire: ???
- B004 : lecture: commutateurs DIP 1, écriture: écran de commutation
- B005 : lire: dip switches 2, écrire: -
- B800 : écriture: commande carte son, lecture: -
Les éléments suivants méritent d'être mentionnés ici:
- L'appareil a BEAUCOUP de ROM (40 Ko) et très peu de RAM (environ 7 Ko, et seulement 4 Ko sont de la «RAM à usage général»)
- Seulement 2 kilo-octets sont alloués pour la "RAM de l'affichage", divisée en deux fragments de 1 kilo-octet, ce qui semble très petit pour un écran couleur 256x256, dans lequel, il semble, les couleurs sont définies pixel par pixel
- Il s'agit d'un système d'E / S dans un schéma d'allocation de mémoire!
Les E / S dans le schéma d'allocation de mémoire sont un peu inhabituelles pour une machine Z80, car l'une des caractéristiques du Z80 est son espace d'adressage 16 bits séparé pour les E / S. Ceci est fait pour économiser de l'espace d'adressage mémoire précieux. Les E / S dans un schéma d'allocation de mémoire se trouvent généralement dans les ordinateurs équipés d'un processeur 6502.
Un coup d'œil au schéma électrique le confirme: la broche IORQ n'est pas détectée sur le CPU de la carte principale, seule la broche MREQ est connectée (qui sert à initialiser la lecture ou l'écriture en mémoire):
Cela signifie que nous n'avons pas à nous soucier des demandes d'E / S pour la fonction de minuterie CPU de la carte principale dans l'émulateur, mais seulement à traiter les demandes de mémoire.
Après avoir étudié le schéma de circuit, j'ai trouvé un autre détail intéressant sur le CPU de la carte principale:
Seule la broche NMI est connectée, tandis que la broche INT maintient toujours un haut niveau de signal d'horloge / elle reste inactive (cela signifie que les interruptions masquées "normales" ne sont pas exécutées et que seules des interruptions non masquées se produisent):

C'est également assez inhabituel pour une voiture avec la Z80. Dans tous les ordinateurs personnels basés sur Z80 avec lesquels je faisais face, l'inverse était vrai - ils n'utilisaient que des interruptions masquables et jamais des non masquables. L'interruption masquée Z80 est une amélioration très flexible et sérieuse par rapport au système d'interruption primitif de son «père illégitime» - Intel 8080, ou son concurrent - MOS 6502. Mais cette flexibilité accrue est en même temps plus difficile à mettre en œuvre dans les équipements (sauf comme source d'interruptions) d'autres puces de la famille Z80 sont utilisées, dans lesquelles il existe déjà un protocole d'interruption complexe intégré lorsqu'elles sont connectées par bus).
Eh bien, assez de détails sur l'équipement, passons à l'émulateur!
Procédure de démarrage
L'étape suivante après avoir déterminé la configuration de la mémoire consiste à connecter le processeur émulé au schéma d'allocation de mémoire émulé, à enregistrer une sorte de visualisation du contenu de la mémoire vidéo et à démarrer les cycles du processeur.
Étonnamment, une telle approche approximative est souvent suffisante pour passer par la procédure de chargement et afficher
quelque chose à l'écran. Lors de la conception de l'émulateur Bomb Jack, je viens de prendre le contenu de la mémoire vidéo de 1 Ko dans la plage de 0x9000 à 0x93FF en tant que matrice 32x32 octets. Lorsque l'octet était 0, j'ai rendu un bloc de pixels noirs 8x8, et sinon - un bloc de pixels blancs.
Ensuite, j'ai simplement exécuté le processeur émulé et espéré le meilleur. Voici! Une sorte d'image lisible est apparue:
L'image du haut ressemble à un écran de test matériel au démarrage, et le bas ressemble à un écran d'enregistrement de score qui apparaît une fois la procédure de démarrage terminée:
... mais tourné de 90 degrés (ce qui est logique, car l'écran des machines d'arcade était souvent dans une orientation verticale "portrait").
Génial, le début est prometteur!
L'étape suivante consiste à comprendre comment transformer ces blocs blancs en pixels de couleur ... (et c'est une étape énorme, les détails sont décrits ci-dessous dans la section sur le décodage vidéo).
Au début, tout s'est passé assez rapidement, sur l'écran de test, les pixels et les couleurs étaient affichés pendant le chargement (plus tard j'ai remarqué que le décodage des couleurs était complètement faux, et pourtant ...):
Mais quand l'écran d'enregistrement devait apparaître, j'ai eu un écran noir. En piratant la couleur d'arrière-plan pour qu'elle ne soit «pas noire», j'ai trouvé que les pixels sont rendus, mais toute la palette de couleurs est noire. Hmm ...
Après avoir regardé cet écran pendant quelques minutes, je me suis souvenu que certaines des couleurs de l'écran des meilleurs scores étaient animées, et lorsqu'il y a animation, il devrait y avoir une sorte de minuteur. La source logique de temps dans cette configuration d'équipement est le signal d'affichage VSYNC, et VSYNC est connecté à la broche NMI du CPU (ou plutôt, non VSYNC, mais VBLANK, qui est le bref instant entre le signal VSYNC et le faisceau de rayons cathodiques se déplaçant vers le coin supérieur gauche).
Et je n'ai pas encore implémenté tout ça ...
Le lendemain soir, lorsque j'ai ajouté la première version du traitement NMI à l'émulation Z80 et que je l'ai connectée au premier compteur vsync / vblank dans la fonction de minuterie CPU de la carte principale, beaucoup de choses ont soudainement commencé à se produire!
Tout d'abord, des couleurs sont apparues sur l'écran des enregistrements, et certaines d'entre elles ont été animées:
Après quelques secondes, quelque chose d'encore plus excitant a commencé! Le meilleur score a disparu et une étrange visualisation de la première carte a été affichée. Il était clair qu'il s'agit d'un mode de démonstration d'une machine d'arcade pour attirer l'attention - j'ai vu plusieurs bombes avec des animations en couleur qui ont disparu lorsqu'un Bomb Jack imaginaire a sauté sur une carte rassemblant ces bombes:
Les couleurs étaient encore complètement fausses, et pourtant c'est du PROGRÈS!
C'est le bon moment pour faire le reste du décodage vidéo:
Fer à repasser vidéo
À première vue, l'équipement de traitement vidéo de Bomb Jack semblait très puissant pour une machine 8 bits de 1984: malgré la résolution de seulement 256x256 pixels, il pouvait afficher simultanément 128 (sur 4096) couleurs et rendre jusqu'à 24 images-objets matérielles (de 16 x 16) ou 32x32) avec une couleur pixel par pixel.
Les ordinateurs personnels 8 bits de l'époque avaient à peu près la même résolution d'affichage, mais ils avaient de nombreuses restrictions de couleurs. Ces restrictions sont très clairement visibles lorsque l'on compare les versions Bomb Jack pour le ZX Spectrum et Amstrad CPC avec la version pour la machine d'arcade:
La
version du ZX Spectrum avait une assez bonne résolution en pixels (256x192), mais très peu de couleurs, et elle souffrait de l'effet typique de «conflit de couleurs» de Spectrum (bien que les développeurs aient fait de leur mieux pour que ce ne soit pas trop perceptible):
La version pour Amstrad CPC est plus en couleur, mais pour obtenir plus de couleurs, les développeurs ont dû passer au mode d'affichage basse résolution (160x200). À la suite de cela, Jack et les monstres se sont transformés en un groupe illisible de pixels:
Comparez cela avec la version de la machine d'arcade, qui avait la même résolution en pixels que le ZX Spectrum, mais avec beaucoup plus de couleurs
et une résolution de couleur pixel par pixel accrue:
Ce qui est intéressant ici, c'est que la version arcade a de meilleurs graphismes, non pas parce qu'elle fonctionne sur un matériel plus puissant (elle a plus de ROM pour stocker plus de données graphiques, mais la "puissance de calcul" est à peu près la même), mais parce que les développeurs de l'appareil pourraient se concentrer sur la fabrication d'une machine spécialisée pour un type spécifique de jeu et ils n'avaient pas besoin de créer un ordinateur domestique universel à usage général.
Voici comment fonctionne le matériel d'affichage (du moins dans mon interprétation de haut niveau):
Trois couches d'affichage
Le signal vidéo Bomb Jack fini est combiné à partir de trois couches: une couche d'arrière-plan, une couche avant et une couche sprite.
Un tel système de couches présente deux avantages principaux:
- Il implémente une compression d'image matérielle assez délicate pour générer une image couleur «haute résolution» à partir d'une très petite quantité de données.
- Il réduit considérablement la quantité de travail CPU nécessaire pour mettre à jour les éléments d'écran dynamiques (même à une fréquence de 4 MHz, un CPU 8 bits n'a pas assez de puissance pour déplacer autant d'objets sur un écran 256x256 avec une fréquence de 60 Hz)
Le fer à repasser vidéo est assez différent de ce que j'ai vu dans les ordinateurs personnels 8 bits, mais MAME implémente des classes d'aide généralisées pour ce type d'équipement, donc je peux supposer qu'il est assez courant dans les machines d'arcade.
Couche d'arrière-plan
La couche d'arrière-plan peut restituer 1 image d'arrière-plan sur 5 intégrée dans la ROM. L'image d'arrière-plan est sélectionnée en écrivant une valeur de 1 à 5 à l'adresse 0x9E00 (il semble que la valeur 0 soit spéciale et rend un arrière-plan complètement noir).
En fait, il semble que l'équipement soit capable de restituer 7 images différentes, mais seulement 5 sont utilisées dans le jeu. J'espérais secrètement trouver des données d'image précédemment non détectées dans la ROM. Mais hélas, ils ne sont pas là (oui, je ne suis probablement pas le premier à les chercher là-bas).
Voici à quoi ressemble la couche d'arrière-plan de la première carte sans les deux autres couches:
La couche d'arrière-plan est assemblée à partir de carreaux
16x16 pixels.
L'avantage de créer des images d'arrière-plan à partir de tuiles est que les mêmes tuiles peuvent être utilisées plusieurs fois, donc moins de données peuvent être stockées dans la ROM. Notez que le ciel bleu, des parties de la pyramide et du sable sous la pyramide utilisent les mêmes tuiles:
Pour économiser de la mémoire, l'équipement de couche d'arrière-plan implémente encore une autre astuce - les tuiles peuvent être tournées horizontalement. J'ai failli manquer cela dans mon implémentation car j'ai supposé que le logiciel n'utilisait pas cette fonction matérielle, mais j'ai remarqué un petit bug en arrière-plan de la troisième carte:
J'ai utilisé la même astuce sur la cinquième carte, mais ici, c'est un peu plus difficile à remarquer si vous ne savez pas quoi chercher:
Couche avant:
Au-dessus de la couche d'arrière-plan se trouve la "couche avant", qui rend toutes les parties fixes de l'écran, qui doivent néanmoins être mises à jour par le CPU (principalement du texte, des plates-formes et des bombes). La disposition est lue à partir de la RAM (à partir de fragments de 1 Ko de RAM et de 1 Ko de RAM couleur).
Voici à quoi ressemble la couche avant isolée de la première carte:
La couche avant est également constituée de tuiles (ainsi que de l'arrière-plan), mais elle utilise des tuiles 8x8 plus petites:
Le principal avantage de diviser l'arrière-plan et l'avant en couches distinctes est que le CPU n'a pas à se soucier du stockage et de la restauration des pixels d'arrière-plan lors de la création ou de la suppression d'éléments avant.
Couche de sprite
Enfin, les sprites matériels sont rendus sur la couche avant. Tout ce qui se déplace sur l'écran est implémenté dans les sprites. L'équipement Bomb Jack peut rendre jusqu'à 24 images-objets, et chaque image-objet peut avoir une taille de 16x16 ou 32x32 pixels. Dans ce cas, les sprites peuvent être positionnés avec une précision pixel par pixel:
Décodeur de tuiles 8x8
Au cœur de l'équipement de décodage vidéo se trouve une palette de couleurs avec 128 éléments et un décodeur de tuiles de 8x8 pixels. La tâche du décodeur de tuiles est de générer un index de palette de couleurs à 7 bits pour chacun des 64 pixels de la tuile.
Ces tuiles 8x8 sont les blocs de construction pour tout ce qui est à l'écran - tuiles de fond 16x16, tuiles de couche avant 8x8 et sprites matériels 16x16 ou 32x32.Voici un schéma de principe de ce décodeur de tuiles 8x8 pour le rendu de la couche avant (si j'ai bien compris):Explication du diagramme de haut en bas:- Le processus de décodage commence en haut par la lecture de l'octet du «code de tuile» de la mémoire vidéo (organisé comme une matrice de codes de tuile 32x32) et un octet séparé de la RAM couleur (également une matrice 32x32). L'obtention des codes de tuiles et de couleurs de la mémoire vidéo ne se produit que pour la couche avant, mais je l'ai ajoutée pour rendre l'image dans son ensemble plus compréhensible. Le décodeur de tuiles 8x8 lui-même ne nécessite qu'une tuile et un code couleur à l'entrée.
- . ( ). , , ( ).
- 8 , 8 ( ). , , 8x8 24 (3 ).
- 64 7- . 3 , 4 — . , , 16 «», 8 . 8 .
- 7- , , 12- RGB- (4 ). ( , , ; , ).
Il s'agit d'un schéma général de décodage de tuiles utilisé par chacune des trois couches d'affichage, mais le décodage de chaque couche est légèrement différent:- La couche avant peut réellement rendre 512 tuiles 8x8 différentes. Cela nécessite des codes de mosaïque 9 bits, mais la mémoire vidéo ne fournit que 8 bits par mosaïque. Le neuvième bit est "emprunté" au cinquième bit de la valeur de couleur (puisque seulement 4 bits de la valeur de couleur sont utilisés pour construire l'index de la palette de couleurs, il reste 4 bits de plus à d'autres fins). Si les 3 bits des couches de bits de tuiles 8x8 sont égaux à zéro, le pixel avant est considéré comme transparent et le pixel d'arrière-plan le «brille».
- 16x16, 16x16=256 256 (512 ). , 16x16 8x8, . , ; «» : 7 , .
- 16x16 32x32 , 4 16 8x8 . , 16x16 96 , 32x32 — 384 . , 3 , .
Pour mieux comprendre à quoi ressemblent les couches de bits de tuile, j'ai écrit un petit programme C qui convertit les tuiles ROM en fichiers PNG (3 bits par pixel convertis en 8 niveaux de gris).Ce qui suit montre les tuiles ROM de la couche avant. Nous voyons les données des nombres et de la police du texte, des tuiles de plate-forme, des bombes (divisées en deux), des parties du logo de l'économiseur d'écran Bomb Jack et le nombre de points multiplicateurs qui apparaissent en haut de l'écran (en passant, tout pivote de 90 degrés, car tout l'écran est également pivoté ):Ensuite, considérez les tuiles ROM de l'arrière-plan. Cela ne semble pas très clair, car ce que nous observons est en fait le décodage de tuiles 16x16 en tuiles 8x8. Chaque tuile 16x16 est créée à partir de quatre tuiles 8x8 adjacentes. Mais vous pouvez reconnaître des parties du temple grec sur la carte 2, le château de la carte 3 et des gratte-ciel de la carte 4.Et enfin, les tuiles sprite ROM. Les sprites 16x16 occupent la moitié supérieure et les sprites 32x32 occupent la moitié inférieure.Un hack intéressant de l'économiseur d'écran Bomb Jack est que le logo est assemblé à partir de tuiles avant et de sprites. Je pense que les développeurs manquaient de ROM de tuiles avant, mais il y avait peu d'espace dans la ROM de sprite:Équipement de sprite
L'équipement de sprite Bomb Jack est très puissant par rapport à ce qui était utilisé dans les ordinateurs personnels de l'époque:- Il pourrait rendre jusqu'à 24 sprites matériels. Il semble qu'il n'y ait eu aucune restriction sur le nombre de sprites par ligne de balayage.
- Les sprites peuvent avoir une taille de 16x16 pixels ou 32x32 pixels
- Chaque sprite peut choisir l'un des 16 emplacements de 8 couleurs dans une palette de couleurs commune
- Les sprites avaient une résolution de couleur pixel par pixel.
- Chaque sprite peut être retourné verticalement ou horizontalement
- Chaque sprite pouvait choisir l'une des 128 images de sprite flashées dans la ROM.
Lors du décodage des pixels et des images-objets d'un système d'images-objets, la même tuile de base 8x8 est utilisée comme dans les couches d'arrière-plan et avant.Les attributs de sprite sont placés dans la plage d'adresses de 0x9820 à 0x987F - 96 octets, 4 octets par sprite. Pour autant que je sache, cette zone est réservée à l'enregistrement; au moins le CPU n'effectue pas d'accès en lecture à cette plage de mémoire.Chaque sprite est décrit par 4 octets:- Octet 0 :
- Bit 7 : s'il est défini, il s'agit d'un sprite 32x32, sinon 16x16
- Bits 6..0 : 7 bits pour définir le code de la tuile sprite utilisé pour rechercher des couches de bits de l'image sprite dans les tuiles ROM.
- Octet 1 :
- Bit 7 : s'il est défini, le sprite est retourné horizontalement
- Bit 6 : s'il est défini, le sprite est retourné verticalement
- Bits 3..0 : 4 bits pour régler la valeur de couleur pour le décodeur de tuiles
- Octet 2 : position du sprite sur l'axe X sur l'écran
- Octet 3 : position du sprite sur l'écran le long de l'axe Y
On ne sait pas exactement ce que font les bits 4 et 5 de l'octet 1, le commentaire dans MAME dit ceci:
e ? (, )
f ? (, (B)?)
Ports d'E / S de mémoire
Quelques notes sur les ports d'entrée / sortie de la carte principale. Comme indiqué ci-dessus, les ports d'E / S ressemblent à ceci:
- 9E00 : écriture: numéro de l'image d'arrière-plan actuelle, lecture: -
- B000 : lecture: état du joystick du lecteur 1, écriture: activation / désactivation du masque NMI
- B001 : lire: état du joystick du joueur 2, écrire: -
- B002 : lire: pièces et boutons Démarrer, écrire: -
- B003 : lire: chien de garde CPU, écrire: ???
- B004 : lecture: commutateurs DIP 1, écriture: écran de commutation
- B005 : lire: dip switches 2, écrire: -
- B800 : écriture: commande carte son, lecture: -
L'adresse 0x9E00 (sélection de l'image d'arrière-plan) que nous avons déjà considérée ci-dessus, et l'adresse 0xB800 (carte son de commande) nous considérerons dans la section suivante. Reste les adresses de 0xB000 à 0xB005:
La lecture des adresses 0xB000 et 0xB001 renvoie l'état actuel des deux joysticks. Les octets définis indiquent les commutateurs fermés du joystick:
- bit 0 : bonne direction
- bit 1 : direction gauche
- bit 2 : vers le haut
- bit 3 : vers le bas
- bit 4 : bouton de saut enfoncé
Les 3 bits restants sont ignorés.
La lecture de 0xB002 renvoie l'état de l'accepteur de pièces et des boutons Démarrer:
- bit 0 : la pièce du joueur 1 est lancée
- bit 1 : la pièce du joueur 2 est lancée
- bit 2 : bouton de démarrage du joueur 1
- bit 3 : bouton de démarrage du joueur 2
La lecture des adresses 0xB004 et 0xB005 renvoie l'état des commutateurs DIP utilisés pour configurer le comportement de la machine d'arcade:
- B004 :
- bits 0,1 : combien de «jeux» sont donnés pour une pièce (1, 2, 3 ou 5)
- bits 2,3 : idem pour le joueur 2
- bits 4,5 : combien de vies par partie (3, 4, 5 ou 2)
- bit 6 : l'emplacement de la borne d'arcade: «table à cocktail» ou «vertical».
- bit 7 : s'il faut lire le son en mode veille
- B005 :
- bits 3.4 : difficulté 1 (vitesse de l'oiseau)
- bits 5,6 : difficulté 2 (nombre et vitesse des ennemis)
- bit 7 : fréquence d'apparition d'une pièce particulière
Enfin, la lecture de l'adresse
B003 implémente un chien de garde logiciel. Le CPU doit souvent lire à partir de cette adresse, sinon la machine d'arcade effectuera une réinitialisation matérielle. Si pour une raison quelconque le jeu plante, l'équipement redémarrera automatiquement.
Vous pouvez écrire sur certaines adresses de port d'E / S:
- B000 : s'il faut générer NMI pendant vblank; semble être désactivé uniquement pendant la procédure de démarrage
- B004 : retourner l'écran entier; Je n'ai jamais rencontré l'utilisation de cette fonction, mais j'ai une théorie à ce sujet (voir ci-dessous)
La fonctionnalité de retournement d'écran est un peu déroutante, car lorsque je joue à un jeu, je n'ai jamais vu son utilisation. Cependant, j'ai une idée de ce qu'il fait, mais pour le confirmer, vous devez écrire du code. Lorsque la borne d'arcade est en configuration «table de cocktail», deux joueurs s'assoient l'un en face de l'autre. Par conséquent, j'ai suggéré que lorsqu'un jeu passe du joueur 1 au joueur 2, cette fonction retourne l'écran. Cependant, je n'ai pas encore implémenté le mode deux joueurs dans l'émulateur.
Carte son
La carte son elle-même est un ordinateur complet doté d'un processeur Z80 (fonctionnant à une fréquence de 3 MHz), de trois puces audio (AY-38910 fonctionnant à une fréquence de 1,5 MHz), ainsi que de la RAM et de la ROM. Le schéma d'allocation de mémoire de la carte son semble assez simple:
- 0000..2000 : 8 Ko de ROM
- 4000..4400 : 1 Ko de RAM
- 6000 : commande sonore de la carte principale
Puisqu'il n'y a rien d'intéressant dans le schéma d'allocation de mémoire au-dessus de l'adresse 0x8000, le contact d'adresse le plus haut du CPU n'est même pas connecté:
L'adresse spéciale 0x6000 est le port d'E / S (verrou 8 bits) situé dans la mémoire, ce qui ne correspond pas à la RAM réelle. Il s'agit du même port que celui situé sur la carte principale à 0xB800. Il s'agit d'un canal de communication entre la carte principale et la carte son.
Les trois puces sonores sont contrôlées par ces instructions de sortie Z80, et non via les ports mémoire. L'AY-3-8910 n'a que deux ports d'E / S ouverts, le premier est utilisé pour stocker le numéro de registre et le second est utilisé pour écrire ou lire le contenu du registre spécifié par le premier port.
Le circuit d'E / S est le suivant:
- 0x00 : première puce sonore: sélection du registre
- 0x01 : première puce sonore: accès au registre sélectionné
- 0x10 : deuxième puce sonore: sélection du registre
- 0x11 : deuxième puce sonore: accès au registre sélectionné
- 0x80 : troisième puce sonore: sélection du registre
- 0x81 : troisième puce sonore: accès au registre sélectionné
Quelques mots sur la puce audio AY-3-8910:
Il s'agit d'un appareil assez standard, très populaire dans les ordinateurs personnels de l'époque (par exemple, dans Amstrad CPC, ZX Spectrum 128, dans les ordinateurs MSX et bien d'autres). L'AY-3-8910 a engendré de nombreuses variations et clones (par exemple, Yamaha YM2149, qui en soi est devenu la base de toute une famille de puces sonores plus puissantes).
L'AY-3-8910 possède 3 canaux de signaux rectangulaires, un générateur de bruit qui peut être mélangé avec trois canaux et un générateur d'enveloppe. Comme il n'y avait qu'un seul générateur d'enveloppe pour les trois canaux, il n'était pas particulièrement utile et la plupart des jeux utilisaient un processeur pour moduler le ton et le volume.
Cela signifie que la puce AY-3-8910 nécessite plus d'intervention du processeur pour créer un son de haute qualité (contrairement aux puces SID plus autonomes, par exemple, dans un ordinateur C64).
Il est étonnant de voir ce qui peut être fait sur trois puces sonores assez simples et sur le processeur qui les contrôle. La musique et les effets sonores de Bomb Jack sont beaucoup plus riches que ce que j'ai entendu dans la plupart des jeux informatiques à domicile.
La seule chose qui est vraiment intéressante dans cette carte son est la façon dont elle reçoit ses commandes de la carte principale.
Verrou de commande sonore
Le «verrou audio» est un stockage à un octet (verrou 8 bits) commun aux cartes son et principale. Le verrou est lié à l'adresse 0xB800 sur la carte principale et à l'adresse 0x6000 sur la carte son.
Lorsque l'interruption NMI est activée à l'aide de VSYNC, la carte son exécute une routine de service d'interruption très simple, qui lit le verrou matériel, l'écrit à l'adresse mémoire normale et définit le «bit de signal», qui indique à la «boucle principale» qu'une nouvelle commande audio a été reçue:
ex af,af' ;0066 exx ;0067 ld hl,04390h ;0068 set 0,(hl) ;006b ld a,(06000h) ;006d ld (04391h),a ;0070 exx ;0073 ex af,af' ;0074 retn ;0075
La méthode d'activation des contacts NMI est légèrement différente de la méthode de la carte principale:
Sur la carte principale, la broche NMI devient active pendant l'exécution de VBLANK.
Cependant, sur la carte son, NMI est activé lorsque VSYNC est déclenché et reste actif non pendant VBLANK, mais jusqu'à ce que la procédure de service d'interruption lit les données du verrou à 0x6000.
Lorsque l'équipement reconnaît la lecture de l'adresse 0x6000, il effectue deux opérations codées en dur:
- contenu du clip audio remis à 0
- Le contact NMI devient inactif
En fait, il s'agit d'une simple élimination du rebond de contact, qui ne permet pas d'exécuter deux fois une commande sonore.
La seule question demeure: à quelle fréquence la carte principale écrit-elle une nouvelle commande (car la façon de mettre en œuvre l'émulation de deux cartes en dépend).
Après le débogage avec printf, j'ai constaté que la carte principale enregistre au plus une commande sonore par trame 60 Hz. Cela a grandement simplifié la structure du "cycle principal" de l'émulateur.
Le problème du travail conjoint de deux ordinateurs émulés distincts qui doivent échanger des données est que l'émulation d'un ordinateur n'est efficace que si elle peut effectuer plusieurs cycles à la fois sans interférence.
Par exemple, le pire des cas serait:
- nous exécutons une instruction dans l'ordinateur 1
- nous exécutons une instruction dans l'ordinateur 2
- répéter ...
Mon émulateur Z80 n'est pas optimisé pour sortir et entrer dans l'émulation pour chaque instruction, car dans ce cas, il doit vider en mémoire et charger à partir de la mémoire l'état du CPU au début et à la fin de chaque instruction. Si le CPU peut traiter de nombreuses instructions sans interférence, vous pouvez stocker (la plupart) l'état du CPU dans les registres et réinitialiser l'état en mémoire sur la dernière instruction.
Autrement dit, une situation idéale serait la suivante: nous effectuons un système émulé sans interférence sur toute la trame du système hôte (pour un processeur avec une fréquence de 4 MHz et à 60 Hz, cela signifie environ 67 000 cycles par trame, soit quelque part entre 3 000 et 16 000 instructions Z80).
Lorsque je travaillais avec Bomb Jack, je devais m'assurer que la carte principale n'enregistrait pas de nouvelle commande avant que la carte son ne puisse lire la dernière commande. Avant de découvrir que la carte principale n'enregistre pas plus d'une commande par trame, j'ai considéré la nécessité de créer une file d'attente complexe de commandes qui intercepterait les enregistrements dans le verrou sonore de la carte principale et stockerait le numéro de cycle et l'octet de commande dans la file d'attente.
Ensuite, au moment où la carte son exécutait sa trame, elle prendrait une nouvelle commande dans la file d'attente de commandes lorsque le numéro de cycle de commande était atteint.
Un tel système fonctionnerait et serait «correct», mais augmenterait considérablement la complexité du code.
Au final, j'ai décidé d'utiliser une solution beaucoup plus simple sans file d'attente. Étant donné que la carte principale n'enregistre qu'une seule commande par trame, j'ai alterné l'exécution sur deux ordinateurs afin que chacun d'eux effectue deux tranches de temps par trame:
- effectuer la première moitié du cadre sur la carte principale
- effectuer la première moitié du cadre sur la carte son
- effectuer la seconde moitié du cadre sur la carte principale
- effectuer la seconde moitié du cadre sur la carte son
Cela garantit que la carte son voit correctement chaque commande enregistrée par la carte principale et peut en même temps exécuter chaque émulation pendant des milliers de cycles.
Bien sûr, le fait que le système hôte fonctionne à une fréquence d'images de 60 Hz est une hypothèse très audacieuse :)
Et le dernier ...
Le dernier fait intéressant sur la version de l'émulateur sur WebAssembly:
Taille compressée de tous les fichiers téléchargés lors de l'exécution de l'émulateur sur WebAssembly
environ égal à 113 kilo-octets:
- environ 2,5 Ko pour HTML, CSS et JS «manuscrits»
- 26,8 ko par fichier JS d'exécution emscripten
- 83,7 Ko par fichier .wasm
Le fichier WASM contient les ROM intégrées de la machine d'arcade.
Non compressées, ces ROM occupent 112 Ko.
Autrement dit, l'
ensemble de l'émulateur compressé avec les ROM intégrées occupe presque le même volume que les ROM non compressées :)
Les ROM de 112 kilo-octets sont compressées à environ 57 Ko, c'est-à-dire que la taille réelle du code compressé dans WASM sans données ROM est inférieure à 30 Ko (84-57).
Cela me semble assez bon pour un émulateur complet d'un système 8 bits;)