Triceraprog
La programmation depuis le Crétacé

VG5000µ, deux mises à jour sur MAME ()

Lors du commentaire systématique de la ROM du VG5000µ, je suis arrivé sur les routines de lecture et écriture sur cassette. Comme d'habitude, afin de vérifier le fonctionnement de la machine, je fais des tests. Si je fais parfois des tests sur le matériel réel, la plupart du temps, un test sur émulateur suffit, voire est beaucoup plus simple, permettant de dérouler une routine et la suivre avec des données bien choisies.

J'utilise essentiellement MAME pour cela, qui est muni d'un debuggeur qui répond à mes attentes. J'utilise parfois dcvg5k, qui est plus orienté sur une utilisation de la machine simple et pratique.

Cependant, pour la cassette, aucun des deux ne convenait. Les deux émulateurs ne savent lire que le format K7, qui a le mérite d'être extrêmement simple et facilement lisible, mais qui a l'inconvénient d'être inadéquat au bon déroulement des routines de lecture et d'écriture.

La format K7 est une simple liste des octets décodés depuis un enregistrement réel. C'est un format pratique car très compact. L'émulateur dcvg5k s'en sert pour injecter ou extraire les valeurs en court-circuitant la routine de lecture et d'écriture de la ROM. Si j'ai bien compris, l'émulateur intercepte l'exécution normale à des endroits bien choisis et prend la main pour une lecture ou écriture très rapide.

C'est une idée qui se défend pour une utilisation de la machine en évitant des temps d'attentes associés aux cassettes.

Dans mon cas d'étude des routines, c'est par contre hors-jeu. J'ai besoin d'un signal audio. Je me suis donc tourné vers MAME pour voir si je pouvais ajouter le traitement d'un fichier .WAV avec ma ROM 1.1 non modifiée.

Ajout de lecture/écriture audio

La première opération a été d'ajouter le support du format .WAV sur MAME.

Comme on peut l'imaginer, la plupart des services de base existent déjà quelque part dans MAME. C'est l'avantage d'un tel mastodonte. Mais être un mastodonte à aussi un inconvénient : il faut savoir ça se trouve et comment ça s'utilise.

Une fois trouvé, c'est assez simple. Dans le fichier src/lib/formats/vg5k_cas.cpp, à côté de la déclaration du support du type de fichier K7, il suffit d'ajouter la déclaration du support .WAV. Toute la gestion de fichier et de magnétophone virtuel est alors pris en charge.

CASSETTE_FORMATLIST_START(vg5k_cassette_formats)
    CASSETTE_FORMAT(vg5k_k7_format)
    CASSETTE_FORMAT(wavfile_format)
CASSETTE_FORMATLIST_END

Premiers problèmes

Je me doutais que cela n'allait pas fonctionner directement, puisque la machine était annotée comme ne fonctionnant pas, à cause d'un problème de lecture à 1200 bauds. J'avais comme deuxième objectif de corriger cela, afin de pouvoir analyser les routines complètement.

Avec la prise en charge du format .WAV, l'émulation réussi à lire un fichier audio sans soucis. Mais méfiance, la routine du VG5000µ se sert de l'amorce du fichier, une suite de signaux haut/bas, pour se calibrer.

Le deuxième test est donc de sauver un fichier .WAV puis de le recharger. Et là, cela ne fonctionne pas. Le fichier est bien sauvé, mais impossible à relire.

En examinant le fichier obtenu, je vois que les timings sont complètement farfelus. Ils ne correspondent pas à la théorie décrite dans le manuel technique, ils ne correspondent ni à 1200 bauds ni à 2400 bauds, et ils ne correspondent pas à la comparaison avec des fichiers écrits par du matériel réel.

Le moteur démarre

Au passage, les manipulations sous MAME sont pénibles, car la gestion du contrôle du moteur du magnétophone n'est pas là. Là encore, la plus grande partie du temps passé est de comprendre comment MAME gère le système. Au final, tout est là, il suffit de le brancher sur le bon signal envoyé sur le port I/O de la cassette.

Chose faite en modifiant la fonction void vg5k_state::cassette_w(uint8_t data) de src/mame/drivers/vg5k.cpp et en y ajoutant cette ligne :

m_cassette->change_state(BIT(data, 1) ? CASSETTE_MOTOR_ENABLED : CASSETTE_MOTOR_DISABLED , CASSETTE_MASK_MOTOR);

J'en profite pour réécrire cette petite fonction pour faire ressortir un peu mieux la sémantique des données envoyées sur le port I/O.

À présent, les fonctions d'accès à la cassette (CLOAD, CSAVE et les autres) contrôlent le moteur simulé du lecteur dans MAME, est c'est bien plus pratique à utiliser !

Le Z80 doit attendre

Retour sur les problèmes de timings. Je vous évite tout le cheminement et les tests quand soudain me vient un doute. Est-ce que l'émulation tourne à la bonne vitesse ? Oui, la fréquence déclarée pour le Z80 est la bonne (même si techniquement, elle devrait découler de celle du VDP), mais cela ne suffit pas !

En effet, le VG5000µ insère un état WAIT supplémentaire pendant sa phase M1 (fetch). Et ça change tout.

Un petit détour par le Z80 ici. Lors de la phase M1 (opcode fetch), qui s'opère en 4 cycles d'horloge, le 2 premiers cycles placent l'adresse du PC sur le bus d'adresse puis signalent une requête de lecture mémoire. Lors du cycle 3 (T3), la valeur de l'instruction est lue depuis le bus de données. Les cycles T3 et T4 conjointement servent pour le rafraîchissement des mémoires dynamiques, laissons ça de côté.

Le Z80 prévoit que le matériel puisse ne pas être prêt à temps pour livrer sur le bus de données l'instruction lue en cycle T3. La ligne WAIT est donc vérifiée à la fin de T2. Si la ligne est validée, alors le Z80 passe en mode WAIT en ajoutant des cycles supplémentaires d'attente, jusqu'à ce que la ligne WAIT soit relâchée.

Et le driver MAME ne respecte pas ça.

Le VG5000µ tourne trop vite ! Les timings sont faux et l'écriture sur cassette ne fonctionne pas. Reste à savoir comment corriger ça.

À la recherche du cycle en plus

Une première piste est d'aller voir comment est gérée cette fonctionnalité dans l'émulation Z80 de MAME. Mauvaise nouvelle, elle ne l'est pas. Ou plutôt, la ligne WAIT est bien émulée, mais uniquement entre les instructions. Cela est bien assez nécessaire, il semblerait, pour l'émulation de son utilisation par des périphériques qui demandent au Z80 d'attendre.

Mais la détection au cycle T2 de la phase M1 n'est pas là. C'est d'ailleurs indiqué dans les commentaires comme une amélioration possible... Que je ne me sens pas d'ajouter.

Deuxième piste : allez voir ce que font les autres. Côté Amstrad CPC et MSX, les tables de timings d'opcode sont ajustées. Il semblerait qu'il y ait d'autres raisons que ce seul état d'attente, même si c'est une des raisons. Les réutiliser seraient une option... pourvu qu'elles soient utilisables, ce que je trouve un peu lourd à vérifier.

Reste que cela m'ennuie car ce n'est pas vraiment ce qu'il se passe dans la machine. Je fouille encore. Je trouve un driver qui annonce que faute d'avoir trouvé comment faire, la machine est trop rapide...

Et enfin, je trouve une solution qui me plaît, utilisée par un autre driver. Utiliser une fonction de rappel sur le rafraîchissement des mémoires dynamiques, qui, étonnamment, est émulée par MAME. Il suffit alors, dans cette fonction de rappel, de dire à l'émulateur du Z80 qu'il devra exécuter un cycle de plus.

void vg5k_state::z80_m1_w(uint8_t data)
{
    m_maincpu->adjust_icount(-1);
}

Ça fonctionne !

Et tout à coup, tous les timings cassette que je surveillais sont corrects ! La sauvegarde génère un fichier audio qui ressemble à quelque chose (même s'il est bien trop carré pour être pris pour un vrai signal sorti d'une machine réelle, mais ce n'est pas bien grave), et surtout, ce fichier est relu sans problème par l'émulateur.

Et ceci est à 2400 bauds comme à 1200 bauds.

Les fichiers sont aussi compris par l'utilitaire de transformation en format K7 qui accompagne dcvg5k.

Des fichiers audio écrits depuis un vrai VG5000µ sont lus aussi. Il me reste le dernier test : lire sur du vrai matériel un fichier audio écrit par MAME. J'ai bon espoir que cela fonctionne, mais faute d'avoir effectué de vrais tests, le driver reste en non fonctionnel.

Support de la touche DELTA

Le VG5000µ possède sur son clavier cette touche non nommée qui est juste désignée par un triangle dans la documentation, connue aussi sous le nom de touche DELTA.

Puisque j'étais dans MAME, pourquoi pas ajouter cette fonctionnalité ? En effet, le soft reset qui consiste à remettre le PC à 0, ce que fait MAME par défaut, ne permet pas de simuler le soft reset tel qu'implémenté sur la machine à travers la touche DELTA.

Or, ce soft reset peut-être routé vers une routine utilisateur pour d'autres usages, comme par exemple, relancer directement un programme.

Une touche à part

Cette touche est à part dans sa connexion au système. Chaque touche du clavier forme une matrice qui est lue à travers le bus d'entrée/sortie du Z80. Mais pas celle-ci.

La touche DELTA est (presque) directement branchée à la ligne NMI du Z80. Autrement dit, appuyer sur la touche provoque une interruption non masquable dans le Z80. Par défaut, cette interruption appelle une routine qui vérifie si la touche CTRL est aussi appuyée et dans ce cas, provoque un soft reset. Tout ceci après avoir appelé une potentielle routine utilisateur qui, de base, ne fait rien.

Comme d'habitude, tout cela est bien entendu pris en charge par MAME, reste à savoir comment. Je suis allé voir du côté d'ancien matériels, où les switchs étaient branchés directement sur des fonctions, sans vraiment constituer un clavier.

La solution trouvée est la suivante. Tout d'abord, déclarer un nouveau port d'entrée/sortie qui associe au changement d'état d'une touche (j'ai choisi la touche End/Fin du clavier, mais c'est configurable par l'utilisateur) une fonction de rappel.

    PORT_START("direct")
        PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_KEYBOARD)        PORT_CODE(KEYCODE_END)                              PORT_NAME("DELTA")          PORT_CHANGED_MEMBER(DEVICE_SELF, vg5k_state, delta_button, 0)

La fonction de rappel signale tout simplement la ligne NMI du Z80 pendant un bref instant :

INPUT_CHANGED_MEMBER(vg5k_state::delta_button)
{
    if (!newval) {
        m_maincpu->pulse_input_line(INPUT_LINE_NMI, attotime::zero);
    }
}

Et voilà. La touche DELTA est émulée. On peut vérifier le fonctionnement des routines utilisateur sur CTRL+DELTA.

Attention, elle ne l'est que lorsque le clavier est en more réel dans MAME, et non en mode naturel, ce dernier mode cherchant une correspondance naturelle entre le clavier de l'hôte et la machine émulée.

C'est où ?

Les deux patchs ont été intégrés dans la branche principale de MAME, c'est donc dès à présent disponible sur le dépôt, ou bien dans une future version officielle de MAME.