Emulateurs SNES à quelques pixels de la perfection absolue


Nous sommes si près de créer un émulateur capable de recréer parfaitement toutes les fonctions du vrai matériel et des logiciels SNES.

Au cours des 15 dernières années, en tant que codeur pour l'émulateur bsnes, j'ai essayé de perfectionner l'émulation Super Nintendo, mais maintenant nous sommes confrontés au dernier problème: la synchronisation précise des cycles d'horloge des processeurs vidéo SNES. Pour atteindre cette dernière étape de la précision de l'émulation, l'aide de toute la communauté est nécessaire, et j'espère pour votre soutien. Mais d'abord, je vais vous dire ce que nous avons déjà accompli.

État actuel


Aujourd'hui, la situation avec l'émulation SNES est très bonne. Mis à part les périphériques inhabituels qui résistent à l'émulation (par exemple, un club de golf avec un capteur de lumière , un simulateur de vélo et un modem téléphonique, utilisé au Japon pour les paris hippiques au Japon), tous les jeux SNES sous licence officielle sont entièrement jouables, et aucun jeu n'a de problèmes évidents.

L'émulation SNES est devenue si précise que j'ai même dû diviser l'émulateur en deux versions: higan , qui vise une précision et une cohérence absolues avec la documentation matérielle, et bsnes , qui vise la vitesse, les capacités étendues et la facilité d'utilisation.

Récemment, dans le domaine de l'émulation SNES, de nombreuses réalisations intéressantes ont été reçues, notamment:


… et beaucoup plus!

Alors, c'est fait? Tout le monde a bien fonctionné, au revoir, et merci pour le poisson? Enfin, pas tout à fait.

Aujourd'hui, nous avons atteint la précision au rythme de presque tous les composants SNES. Les seules exceptions étaient le PPU (unité de traitement d'image, modules de traitement d'image) utilisé pour générer des images vidéo transmises à l'écran. Nous savons surtout comment fonctionnent les PPU, mais pour certaines fonctions, nous devons utiliser des hypothèses, ce qui conduit à une précision imparfaite.

D'une manière générale, les problèmes restants sont assez mineurs. Si vous ne cherchez pas l'idéalité absolument parfaite de l'émulation pour l'amour de l'art, je ne peux pas vous convaincre de la nécessité d'améliorer encore l'émulation PPU. Comme dans n'importe quel domaine, plus nous sommes proches de l'idéal, plus le rendement est faible.

Mais je peux dire pourquoi cela est important pour moi : c'est le travail de toute ma vie, et je ne veux pas que je dise que je suis si près de l'achèvement sans faire le dernier pas. Je vieillis et je ne suis pas éternel. Je veux que la dernière pièce du puzzle soit résolue, de sorte que, ayant pris sa retraite, j'étais sûr que l'héritage SNES est fiable et entièrement préservé grâce à l'émulation. Je veux dire que le problème est résolu .

Si vous êtes toujours intrigué, continuez à lire pour vous familiariser avec le contexte d'un problème et les solutions que je propose.

Modélisation de l'architecture SNES


Commençons par énumérer les composants qui composent SNES:


Schéma du système Super NES.

Les flèches indiquent les directions dans lesquelles les différents processeurs SNES peuvent échanger des données et les lignes en pointillés indiquent les connexions aux puces de mémoire.

La chose la plus importante pour nous maintenant est de remarquer que la sortie de la vidéo et du son est transmise directement depuis PPU et DSP. Cela signifie qu'ils agissent comme des "boîtes noires", et nous ne pouvons pas voir ce qui se passe à l'intérieur. Plus tard, cela deviendra important pour nous.

Exactitude


Imaginez que nous émulions la commande CPU "multiplier", qui prend deux registres (variables), les multiplie, reçoit le résultat et plusieurs drapeaux indiquant l'état du résultat (par exemple, débordement ).

Nous pouvons écrire un programme qui multiplie toute valeur possible de 0 à 255 en tant que facteur et multiplicateur. Ensuite, nous pouvons dériver les résultats de multiplication numérique et indicateur. Ainsi, nous obtenons deux tables de 65 536 éléments.

En analysant ces tableaux, nous pouvons déterminer avec précision comment et où les résultats des calculs CPU sont définis d'une certaine manière. Ensuite, nous pouvons modifier les émulateurs afin que lors de l'exécution du même test, nous obtenions exactement les mêmes tables en même temps.

Supposons maintenant que le processeur puisse effectuer une multiplication 16 bits x 16 bits. Lors du test de chaque valeur possible, 4 milliards de résultats seront générés, ce qui est presque impossible à tester dans un délai raisonnable. Si le CPU a des multiplications de 32 bits x 32 bits, dans la pratique, il ne sera pas possible de tester toutes les combinaisons de valeurs d'entrée avant la mort thermique de l'Univers (au moins au niveau actuel de la technologie).

Dans de tels cas, nous agissons de manière plus sélective dans les tests et essayons de déterminer quand les indicateurs peuvent changer exactement, quand les résultats peuvent déborder, etc. Sinon, nous devions exécuter des tests qui ne se termineraient jamais.

La multiplication est une opération assez banale, mais le même principe peut être étendu à l'ensemble du processus de rétro-ingénierie, y compris les opérations plus complexes, par exemple, la transmission de données via DMA (accès direct à la mémoire) lors du retour horizontal du faisceau. Nous créons des tests qui tentent de déterminer ce qui se passe dans les cas limites, puis vérifions si notre émulation se comporte de manière identique au comportement du vrai SNES.

Générateurs de signaux et battements


SNES possède deux générateurs de signaux (oscillateurs): un oscillateur à cristal fonctionnant à une fréquence d'environ 21 MHz (il contrôle les modules CPU et PPU), et un résonateur en céramique fonctionnant à une fréquence d'environ 24 MHz, qui contrôle SMP et DSP. Dans les coprocesseurs à cartouche, parfois un oscillateur à cristal de 21 MHz est utilisé, et parfois ses propres générateurs de signaux fonctionnant avec d'autres fréquences.


Recréer ce circuit imprimé Super Famicom en code est plus difficile qu'il n'y paraît.

L'horloge est l'élément de base de la synchronisation de tout système, et SNES est conçu pour effectuer diverses tâches avec certaines fréquences et intervalles de temps.

Si vous imaginez une horloge de 100 hertz, ce sera un appareil avec une sortie binaire passant à un état logique élevé du signal (par exemple, +5 V), puis à un état bas du signal (0 V, ou masse) 100 fois par seconde. C'est-à-dire que chaque seconde la tension à la sortie fluctuera 200 fois: en augmentant 100 fois et en abaissant 100 fois le front du signal d'horloge.

Un cycle d'horloge est généralement considéré comme une transition complète, c'est-à-dire qu'un cycle de 100 Hz génère 100 cycles d'horloge par seconde. Certains systèmes nécessitent une distinction entre les fronts montants et descendants, et pour eux, nous divisons le cycle en demi-cycles pour indiquer chaque phase (haute ou basse) du signal d'horloge.

La tâche la plus importante d'un émulateur précis consiste à effectuer des tâches exactement de la même manière et exactement en même temps que sur un équipement réel. Cependant, la façon dont les tâches sont effectuées n'est pas très importante . La seule chose importante est que l'émulateur, recevant les mêmes signaux d'entrée, génère les mêmes signaux de sortie en même temps que sur du matériel réel.

Timings


Parfois, les opérations prennent du temps. Prenons par exemple la multiplication dans le CPU SNES. Au lieu de faire une pause et d'attendre la fin de la multiplication, la CPU SNES calcule le résultat de la multiplication un bit à la fois en arrière-plan pendant huit cycles d'horloge des opcodes CPU. Cela permet potentiellement au code d'effectuer d'autres tâches en attendant la fin de la multiplication.

Très probablement, tout logiciel commercial attendra ces huit cycles, car si vous essayez de lire le résultat avant qu'il ne soit prêt, nous obtiendrons un résultat partiellement terminé. Cependant, avant que les émulateurs SNES ne donnent instantanément des résultats corrects , sans attendre ces cycles d'horloge supplémentaires.

Lorsque les fans de consoles ont commencé à créer et à tester des logiciels auto-écrits dans des émulateurs, cette divergence a commencé à poser certains problèmes. Une partie du logiciel, par exemple, bon nombre des premiers hacks ROM de Super Mario World , ne fonctionnait correctement que dans ces anciens émulateurs, mais pas sur du vrai matériel SNES. Cela est dû au fait qu'ils ont été développés en tenant compte de l'instant (peu fiable du point de vue des équipements réels) d'obtention des résultats de multiplication.

Dans le processus d'amélioration des émulateurs, la compatibilité des anciens logiciels a été rompue, et nous avons donc dû ajouter des options de compatibilité aux nouveaux émulateurs afin de ne pas perdre ces programmes. Oui, peu importe à quel point cela semble surréaliste, mais aujourd'hui, les émulateurs doivent émuler d'autres émulateurs!

La commodité de ce retard de multiplication dans le CPU réside dans le fait qu'il est très prévisible: huit cycles d'horloge de calculs commencent immédiatement après la demande de l'opération de multiplication. En écrivant du code qui lit les résultats après chaque cycle, nous avons pu vérifier que le CPU SNES utilise l'algorithme Booth pour la multiplication .

Synchronisation d'horloge


Les autres opérations ne sont pas faciles à modéliser car elles sont exécutées de manière asynchrone en arrière-plan. Un tel cas est la mise à jour DRAM du processeur SNES central.

Pendant le rendu de chaque ligne raster, l'ensemble du processeur SNES à un certain stade suspend son fonctionnement pendant une courte période de temps pendant que le contenu de la puce RAM est mis à jour. Cela est nécessaire car pour réduire le coût dans SNES, la RAM dynamique (plutôt que statique) a été utilisée comme mémoire principale du CPU. Pour enregistrer le contenu de la RAM dynamique, elle doit être mise à jour périodiquement.


Pour créer un émulateur vraiment parfait, il ne suffit pas d'assurer la jouabilité des trois mille cinq cents jeux SNES. Il est également nécessaire de réaliser la simulation de chaque fonction du système avec une précision de toucher parfaite.

Le facteur clé dans l'analyse des délais exacts de ces opérations a été la possibilité d'utiliser des compteurs PPU horizontaux et verticaux. Ces compteurs effectuent des incréments et sont réinitialisés après chaque déplacement inverse horizontal et vertical du faisceau. Cependant, leur précision n'est que d'un quart de la fréquence du générateur de signal CPU SNES; en d'autres termes, le compteur horizontal incrémente tous les quatre cycles d'horloge.

En lisant plusieurs fois les valeurs des compteurs, j'ai pu déterminer à quel quart du cycle d'horloge le compteur est aligné. En combinant ces connaissances avec des fonctions spécialement créées qui peuvent faire un pas vers le nombre exact de cycles d'horloge indiqué par l'utilisateur, j'ai pu parfaitement adapter le processeur SNES à n'importe quelle position exacte du cycle d'horloge dont j'ai besoin.

Grâce à une traversée itérative de nombreux cycles d'horloge, j'ai pu déterminer quand certaines opérations se produisent exactement (par exemple, mise à jour de DRAM, transmission HDMA, interruption d'interrogation, etc.). Après cela, j'ai pu recréer exactement tout cela dans l'émulation.

Puce SMPLa console SNES possède également ses propres temporisateurs et une rétro-ingénierie réussie a également été effectuée pour ce processeur. Je peux consacrer un article entier uniquement au registre SMP TEST, ce qui permet aux programmeurs de contrôler le diviseur de fréquence SMP et sa minuterie, sans parler d'autres choses terribles. Il suffira de dire que ce n'était pas un processus facile et rapide, mais finalement nous avons gagné.

Nous collectons des coprocesseurs



La puce SuperFX n'est qu'un des nombreux coprocesseurs à cartouche que l'émulateur SNES peut gérer.

Il y a tout un tas de coprocesseurs SNES utilisés dans diverses cartouches de jeu que nous devions également apprivoiser. Des processeurs individuels à usage général comme SuperFX et SA-1 , les processeurs de signaux numériques comme DSP-1 et Cx4 aux accélérateurs de décompression comme S-DD1 et SPC7110, ou les horloges temps réel Sharp et Epson, et bien plus encore ...

Cela signifie que l'émulateur SNES doit gérer les instructions SuperFX et les caches de pixels; avec le schéma de résolution des conflits du bus mémoire SA-1 (permettant aux CPU SNES et SA-1 d'utiliser simultanément les mêmes puces ROM et RAM); avec firmware intégré DSP-1 et Cx4; avec codeurs arithmétiques basés sur la prédiction S-DD1 et SPC7110; ainsi qu'avec des cas limites bizarres de BCD (décimal codé binaire) dans des générateurs en temps réel. Lentement mais sûrement, en utilisant toutes les techniques de détermination de l'exactitude et des temporisations décrites ci-dessus, nous avons réussi à émuler presque parfaitement toutes ces puces.

Il a fallu beaucoup d'efforts et des milliers de dollars pour retirer les capots des puces et retirer le firmware des processeurs de signaux numériques utilisés dans les différents jeux. Dans un cas, l'émulation NEC uPD772x autoriséeutilisez le code de higan pour sauver la voix de feu Stephen Hawking! .

Dans un autre cas, nous avons dû procéder au reverse engineering d'un ensemble d'instructions pour l'architecture Hitachi HG51B, car personne n'avait jamais publié la documentation de cette architecture. Dans un autre cas, il s'est avéré qu'un jeu ( Hayazashi Nidan Morita Shougi 2 ) possède un puissant processeur ARM6 32 bits avec une fréquence de 21 MHz, ce qui accélère le jeu de shogi japonais!

La simple sauvegarde de tous les coprocesseurs SNES s'est avérée être un processus à long terme, plein de difficultés et de surprises.

Traitement des signaux numériques


La puce Sony S-DSP (Digital Signal Processor), qui ne doit pas être confondue avec le coprocesseur à cartouche DSP-1, a généré un son SNES unique. Dans cette puce, huit canaux audio avec codage ADPCM 4 bits ont été connectés, ce qui a permis de créer un signal stéréo 16 bits.

Extérieurement, et à partir du schéma du système présenté ci-dessus, il semble d'abord que le DSP soit une «boîte noire»: nous ajustons les canaux sonores et les paramètres de mixage, après quoi la puce génère du son transmis aux haut-parleurs.

Mais une fonction importante a permis au développeur sous le surnom de blargg d'effectuer une rétro-ingénierie complète de cette puce: c'était un tampon d'écho. Le SNES DSP a une fonction qui mélange la sortie des échantillons précédents pour créer un effet d'écho. Cela se produit à la toute fin du processus de génération du son (à l'exception du dernier indicateur de blocage du son, qui peut être utilisé pour désactiver la sortie audio entière.)

En écrivant du code avec le bon timing des mesures et en suivant l'écho résultant, nous avons pu déterminer l'ordre exact des opérations effectuées par le DSP pour générer de chaque échantillon et créant un son parfait et une précision de battement.

Sauvegarde PPU


Tout cela nous a conduits à la dernière partie du schéma architectural SNES: les puces PPU-1 et PPU-2. Grâce à John McMaster, nous avons scanné les puces S-PPU1 (révision 1) et S-PPU2 (révision 3) avec une multiplication par vingt.


Scanner vingt fois le cristal du premier PPU SNES ...


... et le deuxième PPU.

Les deux analyses de cristal nous font savoir que les puces ne sont évidemment pas des CPU à usage général, ni des architectures spécialisées qui exécutent des codes de fonctionnement à partir de la ROM interne du programme du firmware. Ce sont des circuits logiques séparés avec une logique codée en dur qui reçoivent les signaux entrants de différents registres et de la mémoire et créent un signal vidéo vers le moniteur une ligne raster à la fois.

Les PPU restent le dernier obstacle à l'émulation de SNES car, contrairement à tous les composants décrits ci-dessus, les PPU sont en fait une boîte noire. Nous pouvons les configurer dans n'importe quel état, mais le processeur SNES ne peut pas surveiller directement ce qu'ils génèrent.

Si nous utilisons notre exemple précédent avec multiplication comme analogie, imaginez que vous avez demandé le résultat 3 * 7, mais au lieu de la réponse binaire, vous obtenez une image analogique floue des nombres «21» à l'écran. Toute personne qui exécute votre logiciel pourra voir 21, mais vous ne pouvez pas écrire un programme de test pour vérifier automatiquement s'il voit la bonne réponse. La vérification manuelle par une personne de ces résultats ne peut pas être étendue à plus de plusieurs milliers de tests, et des millions seront nécessaires pour maximiser le comportement PPU.

Je sais ce que vous pensiez: "Mais est-il plus facile d'utiliser une carte de capture, d'effectuer un traitement d'image, de les comparer approximativement avec l'image sur l'écran numérique de l'émulateur, et d'effectuer des tests basés sur cela?"

Et bien oui, c'est possible! Surtout si le test consiste à vérifier deux énormes nombres qui occupent tout l'écran.

Mais que se passe-t-il si le test a de nombreuses nuances et que nous essayons de reconnaître la différence de couleur d'une demi-teinte d'un pixel? Et si nous voulons exécuter un million de tests dans l’ordre et que nous ne savons pas toujours ce que nous allons générer, mais que nous voulons toujours comparer le résultat avec la sortie de notre émulation?

Rien ne vaut la commodité et la précision avec les données numériques - un flux précis de bits qui ne peuvent que correspondre ou ne pas correspondre. La nature analogique d'un signal CRT ne peut pas nous fournir cela.

Pourquoi c'est important?


À l'exception d'un jeu ( Air Strike Patrol ), tous les logiciels SNES sous licence officielle (auraient dû être) basés sur des chaînes raster. Ces jeux n'essaient pas de changer l'état du rendu PPU au milieu de la ligne de raster rendue actuelle (une telle astuce par les programmeurs est appelée "l'effet raster"). Cela signifie que les délais d'exécution de la grande majorité des jeux n'ont pas à être particulièrement précis; si vous avez le temps pour la prochaine ligne de trame complète, alors tout est en ordre.

Mais cela est important pour un seul match.




Cette série d'images montre un effet d'émulation complexe utilisé dans le message «Good Luck» d' Air Strike Patrol .

Dans les images ci-dessus, vous voyez le texte image par image «Good Luck» de Air Strike Patrol . Le jeu l'implémente en changeant la position de défilement vertical du calque d'arrière-plan 3 (BG3). Cependant, l'affichage du tableau de bord à gauche (où vous pouvez voir que le joueur a 39 missiles) est également sur le même calque d'arrière-plan.

Le jeu parvient à effectuer cette séparation en modifiant la position du défilement BG3 dans chaque ligne raster après avoir rendu le tableau de bord gauche, mais avant que le texte «Good Luck» commence à être rendu. Cela peut être fait car en dehors du tableau de bord et du texte, BG3 est transparent et il n'y a rien à dessiner entre ces deux points, quelle que soit la valeur du registre de défilement vertical. Ce comportement nous montre que les registres de défilement peuvent être modifiés à n'importe quelle étape du rendu.


Cette petite ombre sous l'avion a provoqué un tas de maux de tête pour le développeur d'émulateur obsédé par la précision.

L'image ci-dessus montre la tristement célèbre ombre d'un avion. Cet effet est rendu en modifiant le registre de luminosité de l'écran avec de courtes ondulations sur cinq lignes raster.

Pendant le jeu, vous pouvez voir que cette ombre est plutôt chaotique. Dans l'image ci-dessus, elle ressemble un peu à la lettre «c», mais sa forme dans chaque ligne raster change de longueur et de point de départ à chaque image. Les développeurs d' Air Strike Patrol ont juste décrit approximativement où l'ombre devait apparaître et ont résolu ce problème directement. Dans la plupart des cas, cela fonctionne.

Une émulation correcte d'un tel comportement nécessite un timing parfait, ce qui est absolument extrêmement difficile à obtenir dans l'émulateur .


Sur l'écran de pause d' Air Strike Patrol , des effets raster sont utilisés qui n'ont pas été intentionnellement utilisés dans aucun autre jeu SNES.

Parlons maintenant de l'écran de pause. Il active BG3 tout en dessinant une bordure jaune-noir à gauche et le désactive à nouveau pendant la même bordure à droite pour tracer des lignes grises sur l'écran. Il passe également alternativement à travers le cadre les lignes raster dans lesquelles ces lignes grises sont affichées pour créer l'effet d'une gigue de superposition.

Si vous agrandissez l'image émulée ci-dessus, vous remarquerez que pendant la paire de lignes raster dans le coin gauche de ces lignes grises, il manque plusieurs pixels. Cela est arrivé parce que mon émulation PPU est imparfaite à 100% dans les cycles d'horloge. Dans ce cas, cela provoque l'effet d'activer BG3 un peu plus tard qu'il ne le devrait.

Je peux très facilement changer les timings pour que cette image s'affiche correctement. Mais un tel changement est susceptible d'affecter négativement d' autres jeux qui modifient les registres d'affichage PPU au milieu de la ligne raster. Bien qu'Air Strike Patrol soit le seul jeu qui le fasse exprès, il y a au moins une douzaine de jeux dans lesquels cela se produit par hasard (peut-être que l'IRQ les tire trop tôt ou plus tard).

Parfois, cela provoque de brèves dommages visibles à l'image, auxquels il n'est pas fait attention pendant le développement (par exemple, dans Full Throttle Racinglors de la transition entre le magasin et le jeu). Parfois, l'enregistrement est effectué alors que l'écran est par ailleurs transparent et ne provoque donc pas d'anomalies visuelles (par exemple, comme dans le cas de l'affichage du statut HP dans Dai Kaijuu Monogatari II .) Mais même de tels cas de bordure «invisibles» peuvent causer des problèmes dans le rendu moins précis des lignes raster qui sont utilisés dans les émulateurs les plus productifs.

Même si vous ignorez Air Strike Patrol , tous ces effets raster aléatoires (mais valides) dans le logiciel SNES ne vous permettent pas de concevoir fonctionnellement un moteur de rendu PPU qui génère la ligne raster entière avec une précision d'horloge parfaite.

Dans le cas de bsnes au cours des années d'essais et d'erreurs, nous avons créé une liste de ces jeux avec des «effets raster». Nous avons également créé des positions de rendu individuelles qui permettent un rendu beaucoup plus rapide basé sur des lignes raster pour afficher correctement tous ces jeux (à l'exception d' Air Strike Patrol , bien sûr). Mais en substance, il s'agit d'un tas de hacks désagréables pour nous, conçus pour des jeux spécifiques.

J'ai également un rendu PPU basé sur une horloge qui n'a pas besoin de tous ces hacks, mais de temps en temps il crée de petites différences (un à quatre pixels) avec le rendu de cet équipement, comme dans la capture d'écran ci-dessus d' Air Strike Patrol .

Registres de verrouillage internes


La raison de tous ces petits échecs se résume à des instantanés instantanés.

Disons que SNES rend son célèbre mode 7 , qui est une transformation de texture affine avec des changements de paramètres dans chaque ligne raster. Pour déterminer n'importe quel pixel d'écran, vous devez effectuer des calculs similaires:

px = a * clip (hoffset - hcenter) + b * clip (voffset - vcenter) +
b * y + (hcenter << 8)

py = c * clip (hoffset - hcenter) + d * clip (voffset - vcenter) +
d * y + (vcenter << 8)

Le vrai SNES ne pourra pas terminer toutes ces six multiplications assez rapidement pour chaque pixel rendu dans le cadre. Mais aucune de ces valeurs ne change pour chaque pixel (ou, du moins, ne devrait pas changer), nous avons donc juste besoin de calculer px et py une fois au début de chaque ligne raster. Autrement dit, PPU met en cache les résultats statiques dans les verrous, qui sont essentiellement des copies des registres PPU. À l'avenir, ils peuvent être transformés ou rester inchangés.

Ensuite, les coordonnées x, y sont transformées par le mode 7 comme suit:

bœuf = (px + a * x) >> 8

oy = (py + c * x) >> 8

Bien que x varie pour chaque pixel, nous savons que l'incrémentation est effectuée par un à chaque fois. Grâce au stockage des disques internes, nous pouvons simplement ajouter des valeurs constantes a et c à ox et oy pour chaque pixel, plutôt que d'effectuer deux multiplications pour chaque pixel.

Ensuite, la question se pose devant nous: à quelle position particulière du cycle d'horloge le PPU lit-il les valeurs de a et c à partir des registres PPU externes auxquels le CPU a accès?

Si nous les prenons trop tôt, cela peut casser certains jeux. Si nous le prenons trop tard, cela peut casser d'autres jeux.

Le moyen le plus simple est d'attendre les rapports de bogues et d'ajuster ces positions afin de résoudre les problèmes dans chaque jeu spécifique. Mais dans ce cas, nous ne trouverons jamais les positions exactes , seulement leurs approximations.

Et chaque fois que nous modifions l'une de ces variables, il est irréaliste pour nous de retester les trois mille et demi jeux de la bibliothèque SNES pour détecter la détérioration que nos changements pourraient apporter.

De la poêle dans le feu



Interprétation artistique du processus d'élimination des erreurs d'émulation.

Un style similaire de méthodologie de test, "nous ferons juste le jeu que nous voulons travailler à tout prix" a conduit au phénomène, que j'appelle l'émulation "du feu, mais dans le feu".

Au tout début du développement de l'émulation SNES, lorsque des problèmes sont survenus dans le jeu, toute correction dans ce jeu qui lui permettait de fonctionner a été acceptée et ajoutée à l'émulateur. Cette correction a forcément cassé un autre jeu. Et puis ils ont corrigé ce jeu, après quoi le troisième s'est cassé. Fixer à nouveau le troisième match a cassé le premier. Cela a duré de nombreuses années.

L'erreur ici était que les développeurs ont essayé de ne prendre en compte qu'une seule variable à la fois. Supposons que nous ayons un jeu, et pour que cela fonctionne, les événements doivent se produire entre les mesures 20 et 120. Nous ne connaissons pas la mesure exacte, alors choisissez simplement 70, exactement au milieu.

Plus tard, nous obtenons un rapport de bogue dans un autre jeu et déterminons que pour que ce jeu fonctionne , la valeur de la mesure doit être comprise entre 10 et 60. Alors maintenant, nous le changeons en 40, ce qui fonctionne pour les deux jeux. Cela semble logique!

Mais alors le troisième jeu apparaît , dans lequel l'événement devrait fonctionner entre les mesures 80 et 160! Maintenant, nous ne pouvons pas faire fonctionner les trois jeux en même temps avec la même valeur.

Cela a obligé les développeurs d'émulateurs à créer des hacks pour des jeux spécifiques. Les codeurs ne veulent pas libérer d'émulateur dans lequel vous ne pouvez pas exécuter Mario , Zelda ou Metroid . Par conséquent, dans le cas général, le cycle d'horloge 40 est utilisé, mais lors du chargement de Metroid, nous forçons la valeur de synchronisation à 100.

Comment est-ce possible, pourquoi deux jeux ont-ils besoin de valeurs différentes? Cela se produit car non seulement une variable est impliquée ici. La synchronisation que vous avez précédemment utilisée pour déclencher un autre événement peut affecter la valeur de synchronisation requise pour l' événement suivant .

Imaginez ceci sous la forme d'une simple expression algébrique:

2x + y = 120

Vous pouvez le résoudre en prenant x = 10, y = 100. Ou x = 20, y = 80. Ou x = 30, y = 60. Si nous ne pensons qu'à la valeur de x, qui vous permet d'exécuter simultanément un ensemble de jeux, alors nous manquons le fait qu'en fait le problème peut être dans le mauvais y!

Les premières versions d'émulateurs pour augmenter la compatibilité ont simplement redéfini la valeur de x en fonction du jeu en cours. De tels hacks de jeu individuels ont persisté, même si la valeur unique correcte de x a été découverte plus tard. Ainsi , le y problème ne serait jamais résolu!

Cependant, dans le cas de SNES, pas une ou deux variables ne sont impliquées simultanément. La console SNES PPU possède à elle seule 52 registres externes, soit environ 130 paramètres. Dans le processus de rendu d'une seule ligne raster, tous les 130 de ces paramètres et un nombre inconnu de registres internes et de verrous sont impliqués. C'est trop d'informations pour que quelqu'un à l'extérieur puisse réaliser l'état complet du PPU à un moment donné.

Cet aspect de l'émulation n'est pas évident pour les non-initiés, mais il est très juste: la précision n'est pas égale à la compatibilité. Nous pouvons créer un émulateur avec une précision de 99%, capable d'exécuter 10% des jeux. Et vous pouvez écrire un émulateur précis à 80% qui exécute 98% des jeux. Parfois, une mise en œuvre correcte à court terme brise les jeux populaires. Ceci est un sacrifice nécessaire si vous essayez d'obtenir à la fois une précision de 100% et une compatibilité à 100%.

Résoudre le problème


Nous sommes arrivés au stade actuel de l'émulation PPU grâce au raisonnement déductif et aux résultats dans le monde réel.

Nous savons que deux PPU ont accès à deux puces VRAM. Nous savons qu'ils peuvent lire sur chaque puce un nombre connu d'octets de données par ligne raster. Nous connaissons les détails approximatifs du fonctionnement de chacun des modes vidéo SNES. Et sur cette base, nous pouvons esquisser un modèle généralisé de l'apparence de l'architecture. Par exemple, voici un bref exemple du fonctionnement des trois premiers modes vidéo SNES:

if (io.bgMode == 0) {

bg4.fetchNameTable ();

bg3.fetchNameTable ();

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg4.fetchCharacter (0);

bg3.fetchCharacter (0);

bg2.fetchCharacter (0);

bg1.fetchCharacter (0);

}

if (io.bgMode == 1) {

bg3.fetchNameTable ();

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg3.fetchCharacter (0);

bg2.fetchCharacter (0);

bg2.fetchCharacter (1);

bg1.fetchCharacter (0);

bg1.fetchCharacter (1);

}

if (io.bgMode == 2) {

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg3.fetchOffset(0);

bg3.fetchOffset(8);

bg2.fetchCharacter(0);

bg2.fetchCharacter(1);

bg1.fetchCharacter(0);

bg1.fetchCharacter(1);

}


PPU ne révèle à un observateur tiers qu'une petite partie de son état: drapeaux horizontaux et verticaux en arrière (suppression horizontale / verticale), nombre de pixels horizontaux et verticaux et drapeaux de superposition de tuiles dans l'intervalle des sprites. Ce n'est pas tant que ça, mais je le répète - chaque petit élément de l'état accessible à l'observateur nous aide.

La VRAM (RAM vidéo, mémoire vidéo) de la puce PPU pendant le rendu est fermée aux processeurs SNES, même pour la lecture. Mais il s'est avéré que OAM (mémoire de sprite) et CGRAM (mémoire de palette) sont ouverts. L'astuce est qu'à ce moment, le PPU contrôle le bus d'adresse. Par conséquent, en lisant OAM et CGRAM pendant le rendu d'écran, je peux observer ce que le PPU obtient de ces deux blocs de mémoire à un moment aussi critique.

Ce ne sont pas toutes des pièces du puzzle, mais elles me suffisent pour pouvoir mettre en œuvre les modèles pratiquement corrects pour obtenir des sprites.

En utilisant des modèles d'accès pour OAM et CGRAM ouverts, des indicateurs PPU, des observations générales (c'est-à-dire des suppositions) à partir de rapports d'erreur pour différents jeux et un raisonnement déductif, nous avons pu créer des moteurs de rendu PPU basés sur une horloge qui peuvent presque parfaitement lancer tous les jeux publiés.

Mais la situation est toujours précaire: si quelqu'un commence à créer des jeux homebrew en utilisant un timing précis des ticks et des effets raster, alors tous nos émulateurs modernes ne seront pas en mesure de gérer cela. Y compris les implémentations logicielles et matérielles basées sur FPGA.

Je dois dire clairement: aujourd'hui toutils ne connaissent que l'ordre interne des opérations et le comportement d'accrochage des puces PPU de la console SNES. Personne ne sait comment les imiter parfaitement. Au moins pour l'instant.

Solutions possibles


Qu'allons-nous en faire? Comment déterminer l'ordre exact des opérations dans un PPU si, du point de vue du CPU SNES, il s'agit d'une "boîte noire"?

Je vois quatre options possibles: analyseurs logiques, sortie vidéo numérique en mode test, contremarches et retrait des couvercles des puces.

Analyseurs logiques


Si vous regardez les scans de cristaux PPU illustrés ci-dessus, vous remarquerez des zones noires sur les bords de la puce. Ce sont les plates-formes se connectant aux contacts des puces.

Ces broches stockent l'état des puces PPU pendant chaque cycle d'horloge. Ici, vous pouvez trouver l'adresse actuelle à laquelle les puces accèdent à la puce de mémoire vidéo, les valeurs des données transférées d'un PPU au second, et bien plus encore.

Ces informations ne sont pas disponibles pour le code exécuté sur le processeur SNES, mais elles fournissent des observations précieuses sur l'ordre interne des opérations PPU.


La connexion de PPU de console Super NES à un analyseur logique similaire peut être la clé de la boîte noire.

Le problème critique des analyseurs logiques est qu'ils ne sont pas très pratiques à gérer: si vous essayez d'échantillonner des données en direct à partir d'un système qui fonctionne, nous obtiendrons un flux de résultats qui est assez difficile à déchiffrer. Vous rencontrerez le même problème si vous essayez d'analyser la sortie RVB analogique du système: pour capturer ces données, vous devrez effectuer manuellement chacun des tests. Un tel système n'est pas très bon pour créer des tests de régression automatisés reproductibles.

Sortie vidéo numérique en mode test


Récemment, grâce à un balayage de tranches de cristal avec un grossissement de 20x, un mode de test secret a été découvert dans les puces PPU de la console SNES. Si vous apportez une petite modification matérielle, le PPU commencera à émettre un signal RVB numérique 15 bits !

C'est presque ce dont nous avons besoin! Cependant, ce mode a des problèmes, car le célèbre mode 7 ne peut pas y afficher l'image correcte. Il semble que cette fonction ne soit pas complètement remplie.

De plus, pour mettre en œuvre cette méthode, une modification manuelle des consoles SNES et un mécanisme approprié pour capturer et analyser la sortie en mode test sont encore nécessaires. Néanmoins, contrairement à la solution de capture d'un signal RVB analogique, un tel signal numérique peut être soumis à des tests automatiques, ce qui peut nous permettre d'achever rapidement un grand nombre de travaux sur la rétro-ingénierie PPU.

Risers


Étant donné que les PPU sont statiques, nous pourrions extraire les puces PPU d'une console SNES fonctionnelle et les connecter à une carte de prototypage ou à une carte de circuit imprimé personnalisée avec deux puces VRAM. Après cela, vous pouvez placer un microcontrôleur entre le PPU et l'interface USB et connecter l'interface au PC, ce qui permettra à l'encodeur de programmer tous les registres de mémoire vidéo externes et PPU. De plus, l'encodeur pourra contrôler manuellement les cycles d'horloge PPU et lire les signaux résultants sur les connecteurs d'E / S, les registres et dans la mémoire PPU à chaque cycle d'horloge.

En modifiant l'émulateur logiciel pour qu'il génère les mêmes valeurs internes des connecteurs d'E / S, nous pourrions directement comparer le matériel réel avec l'émulation, même en temps réel. Cependant, ce sera un travail très dur car nous ne pouvons pas encore voir les opérations internes du PPU.

Retrait du couvercle


Enfin, la solution la plus extrême consiste à poursuivre l'étude du cristal en retirant le couvercle de la puce. Nous avons déjà des scans cristallins avec un grossissement de 20x, mais leur résolution n'est pas suffisante pour analyser et recréer des circuits logiques individuels, comme cela a été fait dans le projet Visual 6502 . Si nous pouvons obtenir les analyses cristallines des deux PPU avec un grossissement de 100x, alors nous pouvons commencer le dur travail de compilation des circuits PPU et de les convertir en tables de connexion ou en code VHDL. Ensuite, ils peuvent être utilisés directement dans FPGA, ainsi que portés sur C ++ ou un autre langage de programmation, applicable pour créer des émulateurs logiciels.

Un spécialiste qui l'avait fait auparavant m'a donné une estimation approximative: il faudrait environ 600 heures pour cartographier les deux PPU. Cette tâche est beaucoup plus élevée que le niveau de «collectons de l'argent en collectant des fonds et payons quelqu'un», et tombe idéalement dans la catégorie «espérons que quelqu'un de très talentueux avec un ensemble unique de compétences voudra nous aider volontairement».

Bien sûr, cela ne signifie pas que je ne serais pas heureux de récompenser financièrement quelqu'un pour son aide, je peux payer les détails et le travail nécessaires.

Demande d'aide


Pour résumer: je suis allé le plus loin possible dans mon projet d'émulateur SNES, et j'ai besoin d'aide pour mener à bien cette tâche finale. Si vous avez lu jusqu'à la fin, vous voudrez peut-être aider! Tout soutien, y compris la participation au projet bsnes sur GitHub , ou toute documentation de recherche sur le fonctionnement interne des puces PPU, nous sera d'une valeur inestimable!

Merci d'avoir lu et de votre soutien! Ce fut un honneur pour moi depuis quinze ans d'être membre de la communauté d'émulation SNES.

All Articles