Du 68K au ColdFire
Un essai de convertisseur

Nous allons voir dans cet article comment utiliser les nouvelles instructions fournies par le ColdFire 5407 pour faciliter la transformation de code 68K en code ColdFire. Il y a au total 6 nouvelles instructions et 3 autres dont les capacités d'adressage ont été améliorées. Parmi celles-ci, deux instructions sont destinées à faciliter le transfert des données, et en particulier, à convertir en 32 bits des opérandes qui ne le sont pas d'origine. Nous allons nous pencher tout particulièrement sur celles-ci. En voici tout d'abord la description.

mvs
move with sign
mvs <ea>,Dn
taille opérande: 8 ou 16 bits

Transfère la donnée byte ou word vers le registre de destination. Le signe (présent dans le bit de poids fort de la source est étendu à 32 bits pendant le transfert vers la destination.

mvz
move with zero
mvz <ea>,Dn
taille opérande: 8 ou 16 bits

Transfère la donnée byte ou word vers le registre de destination. Les bits restés libres de la destination sont mis à zéro.

add
Dans ces conditions, comment convertir une instruction aussi simple que add.b ou add.w (respectivement addition sur 8 ou 16 bits) alors que le ColdFire n'effectue ces calculs qu'en 32 bits (seule l'instruction add.l est disponible). L'instruction add peut prendre deux formes: add <ea>,Dn et add Dn,<ea>. <ea> signifie "effective address" (adresse effective) et correspond à une donnée issue d'un registre ou de la mémoire et dans ce dernier cas, obtenu par l'un des modes d'adressage permis par le 68K/ColdFire.

Le gros problème est lors d'un accès à la mémoire. Par exemple, pour traîter une chaîne de caractères (où les données sont sur 8 bits), une image (données RGB par groupes de 3 fois 8 bits), du son (données codées sur 16 bits), dans tous ces cas, l'instruction récupèrera en mémoire des données indésirables, puisqu'elle en prendra 32 bits à la fois. c'est pour cela que les instructions move, mvs et mvz travaillent en 8, 16 ou 32 bits. L'instruction move, a en effet conservé ses modes d'adressage, et est donc parfaitement utilisable pour l'émulation, mais les transferts move en 8 et 16 bits n'affectent pas les 16 ou 24 bits inutilisés de la destination, ceux-ci étant pris en compte lors de l'addition, le résultat en est faussé.

Un premier essai

avantaprès
add.b <ea>,Dn mvs.b <ea>,temp
add.l temp,Dn

temp est un emplacement mémoire réservé pour la conversion des instructions. L'idéal est de réserver la S-RAM interne (cadencée à la vitesse du processeur) pour ce genre de transferts (elle ne sert à rien sous TOS), on va alors pratiquement aussi vite que par des registres. Afin de profiter du mode d'adressage absolu court (sur 16 bits, plus rapide) pour l'accès à la S-RAM, elle doit être placée, soit dans les 32 premiers Ko de la mémoire (ce qui n'est pas simple, car cet espace est déjà utilisé par la zone des variables système du TOS), soit dans les 32 derniers (au milieu de la zone des entrées-sorties, en visant bien c'est possible). Note: la zone de S-RAM doit être accessible en mode utilisateur.

avantaprès
add.b Dn,<ea> mvs.b <ea>,temp
add.l Dn,temp
move.b temp+3,<ea>

Vous aurez noté le "+3" ajouté à temp, cela pour transférer le 4eme octet composant temp, c'est à dire l'octet de poids faible.

Les premiers problèmes

Oui, mais cela ne résoud pas tout. Le résultat est juste, mais les codes-condition sont faux! Un exemple: supposez qu'il s'agisse d'une routine graphique destinée à mélanger deux images en les additionnant (on n'effectue pas de moyenne car cela foncerait trop les images). Quand deux zones claires zont mélangées, le résultat doit rester clair, or supposez un pixel clair (valeur 200) mélangé à un autre pixel (valeur 100). La valeur résultante est de 300, le programme chargé de l'addition doit détecter le dépassement de la valeur limite en testant le bit de retenue en remplaçant alors le résultat (300) par la valeur limite (255). En assembleur 68K, cela donne ceci, nous avons mélangé 3 images pour que le problème se produise:

[1] move.b pixel1,d0
[2] add.b pixel2,d0
[3] add.b pixel3,d0
[4] bcc ok
[5] move.b #255,d0
[6] ok:

Converti en ColdFire, on a cela:

[1] move.b pixel1,d0
[2] mvs.b pixel2,temp
[3] add.l temp,d0
[4] mvs.b pixel3,temp
[5] add.l temp,d0
[6] bcc ok
[7] move.b #255,d0
[8] ok:

En fait, on constate que le code traité par un convertisseur automatique est affublé de deux erreurs qui fausseront complètement les résultats. D'une part, les codes-condition sont faux, puisqu'ils sont effectués sur un calcul en 32 bits. Si l'on part de 3 pixels de valeur 100, le résultat sera égal à 300 et il n'y aura eu aucune retenue générée. D'autre part, le move en ligne [1] est effectué en 8 bits (normal, le move a conservé tous ses modes d'adressage). Mais du coup, les 24 bits de poids fort de d0 contiennent des donneés parasites qui fausseront les calculs ultérieurs effectués sur 32 bits. Pour ce dernier point, il suffit de remplacer move.b par mvs.b pour que tout rentre dans l'ordre, mais est-ce que le remplacement systématique des move par des mvs ne risquera pas de cause le disfonctionnement d'autres parties du programme?

Encore d'autres problèmes

Comme si cela ne suffisait pas, n'oublions pas une technique de programmation assez fréquente quand on code en ASM 68K et qui ne supporte pas la conversion.

8 registres de données c'est assez peu (pourtant, c'est énorme par rapport aux 4(!) registres d'usage général (adresses et données) de chez Untel). C'est pour cela que de nombreux codeurs en ASM (dont je fais partie) utilisent l'instruction swap pour stocker deux données indépendantes dans le mot de poids fort et le mot de poids faible d'un même registre. Tant que les opérations effectuées sur ce registre le sont en .b ou en .w, la moitié de poids fort du registre est totalement insensible aux traîtements effectués sur l'autre moitié.

Mais voilà: avec le ColdFire, plus question de cela. Les opérations se font sur 32 bits, et quoique l'on veuille faire, l'ensemble du registre est irrémédiablement affecté par toute opération de calcul (qu'elle soit arithmétique ou logique).

La solution

La seule solution pour éviter cela est d'effectuer à l'extérieur du registre les opérations de calcul. Comme l'instruction add s'effectue forcément depuis ou vers un registre, c'est le contenu du registre qui va être sauvé en S-RAM le temps du calcul.

L'instruction add est alors convertie comme suit (attention: il y a des bidouilles):

add.b
avantaprès
add.b <ea>,Dn [1] move.l Dn,temp
[2] clr.l temp+4
[3] move.l <ea>,Dn
[4] add.l Dn,temp+3
[5] move.w ccr,Dn
[6] move.w Dn,ccr_temp
[7] move.l temp,Dn
[8] move.w ccr_temp,ccr

Quelques explications:
[1] Le contenu du registre est sauvegardé dans la S-RAM.
[2] On efface les données placées après dans la S-RAM, ça va servir plus bas. En fait, seuls 3 octets ont réellement besoin d'être effacés, mais il est plus simple d'en effacer 4.
[3] La donnée à additionner est stockée dans Dn, car l'instruction d'addition doit forcément avoir un registre de données comme source ou destination. Notez le début de la bidoulle: seul 1 octet devrait être additionné, mais on en charge 4, cela a pour effet de placer dans l'octet de poids fort de Dn la donnée à additionner et dans les 3 autres octets des données parasites.
[4] L'addition est effectuée. Suite de la bidouille: en effectuant l'addition trois octets après le début de temp, l'octet de poids fort précédemment dans le registre (qui contient la donnée à additionner) est ajouté à l'octet de poids faible du registre sauvegardé en mémoire (qui correspond également à la donnée à additionner). Les 3 octets de poids faibles sont additionnés aux zéros qui ont été placés à l'étape [2], ce qui est sans influence sur le résultat utile, qui est dans l'octet de poids fort.
[5] Les codes-conditions du résultal sont sauverardés avant le move car ce dernier modifie les valeurs du ccr (condition codes register).
[6] Comme le move depuis le ccr ne peut être fait que vers un registre de données, la valeur est tranférée en deux étapes.
[6] Le contenu sauvegardé du registre, ainsi que l'octet de poids faible, qui contient le résultat du add.b retournent dans le registre.
[7] Les codes-conditions sont restaurés.

Cette façon de procéder s'apparente à une addition en virgule fixe avec 8 bits de partie entière et 24 bits de partie décimale. Seule la partie entière est prise en compte pour le résultat. Cette façon de procéder permet d'obtenir des codes-condition valides sans calculs supplémentaires.

Note: Le fait de lire 3 octets inutiles peut entraîner des erreurs de bus quand on est à la fin d'un bloc mémoire. Le trap de gestion des erreurs de bus doit être à même de récupérer ce type d'erreur.

Je vous laisse la joie d'écrire le code d'émulation du add.b Dn,<ea>. Celui-ci devrait faire une à deux lignes de plus.

- Pascal Barlier - 21 fev 2001 - coldfire.online.fr -