Triceraprog
La programmation depuis le Crétacé

VG5000µ, une Cartographie de la Mémoire BASIC ()

Quand j'ai commencé à étudier les documentations sur le VG5000µ, des incohérences sur certains points me sont apparues. Les différentes sources ne donnaient pas les même renseignements. Ou alors je lisais mal. Mais un truc ne collait pas.

En étudiant la ROM, là encore, ça ne collait pas avec les documentations. Mais le code ne ment pas, j'ai donc débuté une quête de la vérité : que se passe-t-il vraiment ?

... ce n'était peut-être pas si épique, mais bon...

L'anomalie qui m'intéresse aujourd'hui est celle de la gestion de la mémoire par le BASIC. Et voici les pièces qui ont provoqué mon étonnement.

Première pièce

La manuel de l'utilisateur, page 46, indique que le premier paramètre de la commande définit la totalité de l'espace occupé par les chaînes de caractères et doit être compris entre -32768 et 32767. Spécifier une mémoire à réserver négative est un peu étrange, et un test rapide montre que passer une valeur négative est rejetée par le BASIC.

Au passage, un coup d'œil dans la ROM montre que l'instruction CLEAR utilise une routine qui ne décode que des entiers positifs...

Plus étonnant, le second paramètre, qui indique la plus haute adresse possible atteignable par le BASIC doit être inférieur à 32767 pour le VG5000 standard, et 42864 pour l'extension mémoire 16ko. Et ne mentionne pas 32ko d'extension.

42864... Ça ne correspond pas à grand chose. Avec l'extension 16ko, je vois plutôt une adresse maximale à 49151. En effet, la ROM prend les 16 premiers ko, et les 16+16 ko de RAM sont à la suite. 48 * 1024 donne 49152 octets.

Bref, louche. De plus, un essai direct d'un CLEAR 50, 42864 ne fonctionne pas, un paramètre invalide est indiqué. La routine pour décoder le second paramètre étant une routine qui prend des entiers entre -32768 et 32767.

Une cartographie de la mémoire page 86 indique que la mémoire peut bien aller jusqu'à 49151, qu'il faut spécifier cette valeur en complément à 2 sous une forme négative. C'est incohérent avec la description de CLEAR mais l'expérience montre que la cartographie a raison. Elle indique aussi des valeur de pointeur (stktop), (fretop), (arytab) et (txttab), mais n'indique pas où les trouver.

Deuxième pièce

La deuxième source est « Clefs pour VG5000 », un livre des éditions du P.S.I.

Page 15, la description de CLEAR est globalement reprise avec les mêmes renseignements erronés.

Page 69, une cartographie de la mémoire est disponible et indique des noms de variables pointeurs sur des zones mémoire. Plus loin, ces variables systèmes sont décrites, avec leurs adresses. C'est par ici que j'avais commencé à regarder le fonctionnement.

Deux choses me paraissaient bizarre. Une flèche entre arytab et fretop sans nom. Soit. Mais aussi une variable fretop qui est sous la pile. C'est bizarre car le bas de la pile, c'est déjà le registre SP, quel intérêt de garder une information comme ça ? Pour protéger la pile ?

C'est cependant cohérent avec le manuel d'utilisateur et donne une information en plus sur le fait que (memsiz) n'est pas initialisé tout en haut de la RAM disponible mais trois octets avant. Il ajoute une variable de système : (vartab).

C'est à ce moment là que j'ai commencé à étudier la ROM et voir que (fretop) était initialisé avec la valeur (memsiz), et que lors d'allocation de chaînes de caractères, l'algorithme partait du principe que (fretop) était toujours supérieur à (stktop).

En fait, l'erreur d'allocation de chaîne était lancée, si je ne me trompais pas (mais le doute est permis quand on regarde des pages de code assembleur), lorsque (fretop) atteignant (stktop) par le haut. Autrement dit, tant que (fretop)-(stktop) était positif, il y avait de la place. Je détaille ça plus loin.

Et donc, ça ne collait pas avec le schéma.

Les descriptions des variables sont les suivantes :

  • $488E : adresse de début du programme Basic (txttab)
  • $4895 : Adresse du haut de la pile (stktop)
  • $49C3 : adresse du haut de la zone "chaînes" (fretop)
  • $49D8 : adresse de début de la zone "variables" (vartab)
  • $49DA : adresse de début de la zone "chaînes" (arytab) (c'est incohérent d'après le schéma, mais on se doute bien que ary signifie array est qu'il s'agit en fait de la zone pour les tableaux DIM)
  • $49DC : adresse de fin du stockage en cours, n'est pas nommé. Je ne le savais pas à ce moment-là, mais il s'agit de (strend) et c'est le nom qu'il manque entre (arytab) et (fretop)... il faut dire que la description n'est pas hyper parlante. Quel stockage ? En cours de quoi ?

Troisième pièce

Le troisième document dans lequel je me suis plongé est celui du manuel technique, en anglais. Page 6, on trouve le tableau source des deux précédents documents., mais là encore, (fretop) est sous la pile...

Plus loin, page 9, la liste des variables systèmes, là encore probablement la source du livre P.S.I, est donné. On y trouve (txttab), (stktop), (fretop), (vartab), (arytab) avec sa description correcte mentionnant des tableaux, et (fretop), et (strend) indiquant « end of storage in use ».

La description de (strend) est un peu plus éclairante que sa traduction dans le livre P.S.I, même si ça manque de détail. Il n’apparaît pas dans la cartographie de la mémoire.

Il n'y a pas d'autres mentions du fonctionnement des allocations mémoire dans ce document, à part un peu à propos de manipulation de (txttab) et (vartab) quand on veut lancer un programme BASIC depuis un ROM d'extension.

Quatrième pièce

De tous les documents sur le VG5000µ, les plus dignes de confiances, ceux qui vont un peu plus dans le détail et dans lesquels je n'ai jamais trouvé d'erreur jusqu'à maintenant, ce sont les « Technical Bulletin** ». Sur celui du 14 juin 1984, à propos de « BASIC Text Relocation » il est mentionné que (strend) sera bougé, avec (vartab), (arytab) et (temp), si (txttab) est modifié et que le BASIC exécute une des routines qui replace tout ça.

Pas tellement plus d'information cependant.

Dans le bulletin du 11 septembre 1984, une mention de (stktop) et (memsiz) est fait, qui donne les bonnes adresses en fonction des différentes configuration de mémoire.

Au final...

Au final... et bien le mieux est d'aller voir dans la ROM ce qu'il se passe. Et pour chercher, je pars sur deux pistes : où donc est-ce que (fretop) est utilisé, et qu'est-ce que (strend) ?

(fretop) est utilisé a de nombreux endroits, mais quelques-uns de ses usages sont suffisant pour comprendre.

L’initialisation

La fonction suivante est appelé lorsqu'il s'agit de réinitialiser la mémoire. Au lancement de la machine, par un appel à NEW, mais aussi directement en reset_vars lors d'un RUN ou en init_vars lors d'un CLEAR.

reset_mem:   ld       hl,(txttab)          ; $2ed9
reset_mem_2: xor      a,a
             jp       reset_mem_3

reset_mem_4: inc      hl
             ld       (vartab),hl
reset_vars:  ld       hl,(txttab)
             dec      hl
init_vars:   ld       (temp),hl
             ld       hl,(memsiz)
             ld       (fretop),hl
             xor      a,a
             call     inst_restore
             ld       hl,(vartab)
             ld       (arytab),hl
             ld       (strend),hl

Tout commence avec comme point de repère (txttab), qui est le début du programme BASIC en cours.

Le jp reset_mem_3 renvoie en reset_mem_4 après avoir coupé le lien sur la première ligne de BASIC, effaçant par la même occasion l'accès au programme (qui est toujours là...).

Cette gymnastique avec reset_mem_3, qui n'est jamais appelé par ailleurs, vient à mon avis du patch de la ROM 1.1, qui fait tout pour rester stable dans les adresses de routines. Je vérifierai cette théorie plus tard.

(vartab) est placé deux octets plus loin que (txttab) (HL est aussi incrémenté par le code de reset_mem_3)

(temp) est placé un octet avec (txttab)

(fretop) est initialisé à la même adresse que (memsiz), ce qui montre bien que, au moins là, les cartographie mémoire sont fausses.

L'appel à inst_restore est l'exécution de l'instruction RESTORE du BASIC, qui va chercher et stocker la première ligne contenant des DATA.

Puis enfin, (arytab) et (strend) prennent la valeur de (vartab).

Un peu plus loin, les lignes suivantes remettent le registre SP à l'adresse en haut de sa zone, stockée dans (stktop).

             ld       hl,(stktop)          ; $2eff
             ld       sp,hl

La conclusion sur l’initialisation de la mémoire est que les adresses de références sont (txttab), (memsiz) et (stktop). Les autres pointeurs sont placés en fonction de ces adresses.

Vérification mémoire de chaînes

Lorsqu'une chaîne de caractère est sur le point d'être créé, la routine suivante est appelée en premier lieu :

             ld       hl,(stktop)          ; $36bf
             ex       de,hl
             ld       hl,(fretop)
             cpl
             ld       c,a
             ld       b,$ff
             add      hl,bc
             inc      hl
             rst      de_compare
             jr       c,out_str_mem

À l'entrée de cette routine, A contient le nombre de caractères de la chaîne à créer.

Après les trois premières lignes, on se retrouve avec (stktop) dans DE et (fretop) dans HL. Les trois lignes suivantes mettent dans BC l'inverse de A (en complément à 2).

Via le add, HL contient donc (fretop) moins le nombre de caractères dont on a besoin, (le inc qui suit est la correction de la soustraction en complément à 2).

rst de_compare est une routine qui compare les valeurs de HL et DE, si HL est inférieur à DE, alors le flag Carry est levé, ce qui provoque le saut qui suit vers l'erreur indiquant qu'il n'y a pas assez de mémoire dans l'espace réservé aux chaînes de caractères.

Ce qui est important ici, c'est que ce calcul montre que (fretop) doit être supérieur à (stktop). Ce qui montre encore que les schémas sont faux.

FRE(" ")

Dernière vérification, histoire d'être vraiment certain, en analysant la fonction FRE(" "). Cette commande avec une chaîne en paramètre n'est pas documentée dans le manuel d'instruction, mais on la trouve dans les « bulletins ».

Lorsque FRE a un paramètre numérique (peu importe lequel), la fonction renvoie la place restante en mémoire BASIC... pour tout ce qui n'est pas stockage des caractères de chaînes.

Avec un paramètre alphanumérique, FRE renvoie l'espace restant dans la mémoire réservée aux caractères.

Et en voici le code :

inst_fre:    ld       hl,(strend)          ; $38b1
             ex       de,hl
             ld       hl,$0000
             add      hl,sp
             ld       a,(valtyp)
             or       a,a
             jp       z,inst_fre_2
             call     gstrcu
             call     call36e3
             ld       de,(stktop)
             ld       hl,(fretop)
             jp       inst_fre_2

inst_fre_2:  ld       a,l
             sub      a,e
             ld       c,a
             ld       a,h
             sbc      a,d

Tout commence en plaçant (strend) dans DE (via HL). Puis en mettant SP dans HL (via le add).

(valtyp) contient le type de l'expression qui vient d'être évaluée. À l'appel d'une fonction, il s'agit de la valeur du paramètre. Si cette valeur est numérique (= 0), la suite se passe en inst_fre_2 où le résultat de l'opération HL - DE est mis dans le couple de registres A et C.

Au passage, notons que la mémoire restante est calculée comme la différence entre le pointeur de pile courant et (strend), ce qui permet de situer correctement la fonction de (strend) comme marquant la fin (adresse haute) du stockage BASIC « listing + variables + tableaux ».

Si l'expression était alphanumérique, alors il y a deux appels (dont un auquel je n'ai pas encore donné de nom) qui permettent d'appeler le ramasse miettes (Garbage Collection) sur les chaînes de caractères. Passons.

Puis (stktop) est placé dans DE et (fretop) dans HL, avant d'effectuer le même calcul que précédemment HL - DE.

Ce qui montre à nouveau que (fretop) doit être supérieur à (stktop) et qui indique même que cet espace correspond à la zone de mémoire libre pour les chaînes. Autrement dit, (fretop) présente l'adresse la plus basse des chaînes de caractères, toutes stockées au-dessus. Le nom a probablement provoqué la confusion de sa description comme étant l'adresse du haut, mais c'est en fait une zone qui croît vers le bas.

En donc ?

(fretop) est un pointeur qui est valide entre (memsiz) et (stktop). L'espace entre (memsiz) et (stktop) est déterminé par le premier paramètre de CLEAR et (memsiz) est déterminé par le second paramètre de CLEAR.

Et grâce à ces informations, nous pouvons cartographier, la mémoire avec ses pointeurs de manière correcte et complète, du moins je l'espère.

Cartographie Mémoire VG5000µ