Introduction au Motorola ColdFire

| Joris Dedieu | Alcatîz |

Présentation du jeu d'instruction et du modèle de programmation des microcontrôleurs Motorola ColdFire. version html (en ligne)
version html (zip)
version pdf
les exemples

- Titre : Introduction au Motorola ColdFire

- Auteurs : Joris Dedieu, Alcatîz

- Parution : 5 décembre 2005

- Mise à jour : 5 décembre 2005

- Licence : Copyright ® 2005 Joris Dedieu. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Contexte

- I-B. Limites de ce travail

- I-C. Remerciements

- II. Fondements

- II-A. Architecture

- II-B. Registres

- II-B-1. Les registres de donnée

- II-B-2. Les registres d'Adresse

- II-B-3. Les registres de contrôle

- III. Modèle de programmation

- III-A. Syntaxe

- III-B. Modes d'adressage

- III-C. Jeu d'instruction

- III-C-1. Les instructions de déplacement

- III-C-2. Les instructions logiques

- III-C-3. Les instructions arithmétiques

- III-C-4. Décalages et permutations

- III-C-5. Opérations sur les bits

- III-C-6. Tests, comparaisons, branchements

- III-C-7. Instructions de contrôle et divers

- III-D. Exemples

- III-D-1. Le codage BCD

- III-D-2. Addition de deux mots de 64 bits

- III-D-3. Parcours itératif d'un tableau d'octet

- IV. Gestion du contexte et des variables

- IV-A. Gestion de la pile

- IV-B. Départ en sous-programme

- IV-C. Gestion des variables

- V. Gestion des exceptions

- V-A. Mécanisme général

- V-B. Reset

- V-C. Les erreurs

- V-D. Les périphériques

- V-E. Les exceptions programmables

- VI. L'unité MAC

- VII. Conclusion

- Annexes

- Récapitulatif Instructions Adressage

- Simulateurs, compilateurs ...

- Liens

I - Introduction

I-A - Contexte

Lorsqu'on apprend l'assembleur, c'est souvent pour découvrir le fonctionnement intime d'une machine. En ce sens, le couple 8086/MS-DOS est un formidable outil. Le contexte rassurant fourni par le DOS permet de s'affranchir de bien des difficultés. De même en production, lorsqu'on insère des blocs assembleur dans du C ou du Pascal, pour optimiser un calcul gourmand ou pour appeler un jeu d'instruction spécifique, on reste dans un cadre strict défini par le système d'exploitation.

Copyright material owned by Freescale Semiconductor, Inc. used with permission, 2005.

En informatique industrielle la problématique est souvent différente puisque l'utilisation d'un système d'exploitation n'est pas systématique. Bien souvent un simple moniteur en ROM suffit. De plus il faut pouvoir gérer finement les contraintes de place et garantir des temps d'exécution. Que l'on code en C, Ada ou directement en assembleur, la connaissance approfondie des mécanismes internes de la machine est alors une nécessité.

Nous allons découvrir une famille de machines, particulièrement bien pensée pour les problématiques industrielles : les Motorola ColdFire (MCF).

Pour ceux qui sont très jeunes ou qui ne s'intéressent pas à ces questions, les processeurs Motorola ont connu leur heure de gloire, notamment grâce à la famille des 68000 (on parlera de M68k) qui équipaient les machines de nombreux constructeurs : les Atari ST, les Amiga, les Macintosh (jusqu'au LC 630), ainsi que les stations SGI et SUN avant l'apparition des processeurs MIPS et SPARC, des calculatrices Texas Instruments ... etc...

Le marché des ordinateurs personnels ayant été délaissé au profit des PowerPC développé avec IBM, Motorola semiconductors (depuis devenu Freescale) redéploya la famille des 68000, moyennant des changements architecturaux de poids, dans toute une déclinaison de microcontrôleurs RISC : les DragonBall destinés au PDA et autres smartphones et les ColdFire .

I-B - Limites de ce travail

L'objet de ce travail est vaste, voire infini. Nous nous efforcerons de rester dans les limites du processeur embarqué. Cela signifie que nous ne détaillerons pas les unités périphériques, mémoires, caches et coprocesseurs associés à ces machines. Nous nous efforcerons également d'avoir un propos générique, même si notre expérience ne nous permet pas d'affirmer la validité de ce travail pour toutes les variantes de la famille. Le 5307 et le 5206e nous serviront de référence.

Un petit nombre d'instructions de contrôle ainsi que quelques registres particuliers seront volontairement ignorés ici.

L'objet de ce travail est donc clairement de se familiariser avec l'assembleur Motorola, la machine et ses limites, ainsi que la programmation sans système d'exploitation.

Des connaissances minimales en architecture machine et programmation seront nécessaires pour comprendre la suite : décodage et exécution d'une instruction, arithmétique binaire ...

I-C - Remerciements

Merci à Martine et Olivia ainsi qu'à Alcatîz pour ses conseils avisées et les corrections orthographiques. Merci également à Rhonda Tanner de Freescale Semiconductor.

II - Fondements

II-A - Architecture

Un microcontrôleur n'est pas simplement un microprocesseur mais un ordinateur complet. Cela signifie qu'il embarque de la mémoire, des coprocesseurs (MMU, FPU, MAC...), des unités périphériques (coupleur parallèle, timers, port série ...).

Les ColdFire sont des microcontrôleurs RISC 32 bits, big-endian. Typiquement, on les trouvera dans des imprimantes réseau, des modems routeurs, des appareils d'acquisition numérique et toutes sorte d'ordinateurs embarqués.

La famille est vaste. Par exemple, le 5407 est profilé pour des applications industrielles de base (bus I2C, port parallèle, port série) alors que le 5485 doté d'une MMU et d'une FPU, possède des interfaces de plus haut niveau (PCI, USB, Ethernet, bus CAN). Ils supportent des cadences jusqu'à 200Mhz ainsi que des environnements hostiles (-45 C à +85 C). Leur coût est globalement modique. Compter de 5$ à 25$ suivant les modèles (prix constructeur).

Copyright material owned by Freescale Semiconductor, Inc. used with permission, 2005.

Les ColdFire sont donc de petites machines très performantes et peu chères destinées aux applications industrielles.

RISC ou CISC :
A proprement parler, les ColdFire ne sont pas des RISC. En effet, beaucoup d'instructions s'exécutent en plus d'un cycle. Ce qui est contraire à la philosophie du RISC. Pourtant on conviendra que ce n'est pas non plus un CISC. Le jeu d'instruction est réduit. Pour celà, et pour mettre en avant la souplesse du modèle de programmation, Motorola a inventé la notion de Variable-Length ou VL-RISC. Sans entrer plus en avant dans le débat, nous parlerons ici de RISC en convenant que les ColdFire ne sont pas des PowerPC.

Le noeud de ces caractéristiques est avant tout l'architecture RISC. Le jeu d'instruction se limite à des commandes simples et presque toutes câblées (on parle d'instructions câblées par opposition aux instructions micro-codées). Une instruction câblée s'exécute en quelques cycles (voire un seul) alors qu'une instruction micro-codée utilise un algorithme plus complexe et demande en général quelques dizaines de cycles. Par exemple sur le 5307, les divisions prennent au moins 35 cycles là ou un décalage n'en prendra qu'un.

L'utilisation d'instructions micro-codées est lourde de conséquences et doit toujours être évaluée avec soin.

La capacité des MCF à supporter sans perte de performances des opérandes en 8, 16 ou 32 bits apporte beaucoup de souplesse. Ceci dit, en ce qui concerne l'adressage mémoire, on observe une chute des performances si les données ne sont pas alignées. Par exemple pour stocker le résultat d'une addition sur 64 bits (résultat sur 65 bits), il vaut mieux utiliser 3x32 bits ou 12x8 bits que 2x32 bits et un octet (cela est néanmoins possible). A moins d'être fortement contraint par la mémoire, la meilleure stratégie consiste à choisir une taille d'opérande et à s'y tenir. De plus, lors des départs en exception, le MCF aligne la pile sur un multiple de quatre. On peut donc dans une moindre mesure gagner un peu de ce côté la.

Comme nous l'avons dit le ColdFire est big-endian. Cela signifie que l'octet de poids fort est toujours du coté de l'adresse faible.

La présence d'un module de débugage permet lors de la mise au point des programmes de suivre très simplement ce qui se passe à l'intérieur du processeur.

Enfin signalons, qu'outre les protections de la mémoire que nous n'aborderons pas ici, le ColdFire possède un mode utilisateur simple et un mode superviseur. Cela permet de réduire très simplement les privilèges d'un programme.

Puisque nous avons fait le tour des généralités concernant les ColdFire nous pouvons commencer à nous aventurer à l'intérieur...

A retenir :
Un ColdFire est un microcontrôleur, c'est-à-dire un ordinateur complet basé sur un processeur d'architecture VL-RISC. Cela signifie qu'on peut utiliser des opérandes sur 8, 16 ou 32 bits en faisant toutefois attention à l'alignement des données en mémoire. Par ailleurs les rares instructions micro codées (division) sont bien plus lentes que les autres instructions. Leur usage doit donc être jaugé avec soin. Enfin on ne le répétera jamais assez aux "Intelistes" : L'octet de poids fort est du côté de l'adresse faible.

II-B - Registres

II-B-1 - Les registres de donnée

Ils sont au nombre de huit. Généralistes, ils ont un comportement strictement équivalent :

Tous ces registres font 32 bits. Les registres de donnée peuvent être utilisées en mode 8bit (.B), 16bit (.W) ou 32bit (.L) :

- MOVE.L D0,D1 déplacera les 32 bits de D0 dans D1

- MOVE.W D0,D1 déplacera les 16 bits de poids faible de D0 dans D1

- MOVE.B D0,D1 déplacera les 8 bits de poids faible de D0 dans D1

Leur utilisation est clairement indifférenciée. Aucun n'a de propriété particulière.

II-B-2 - Les registres d'Adresse

Egalement au nombre de huit :

Utilisables en mode 16 (.W) ou 32 (.L) bits, seul A7 a un rôle particulier puisque c'est le pointeur de pile système (aussi appelé SP). Il ne doit donc être utilisé qu'en connaissance de cause. A6 n'est généralement utilisé que pour la gestion des variables mais il n'y a rien d'obligatoire à cela.

Même si cela est fortement déconseillé pour des raisons de performance et de lisibilité, on peut très bien utiliser un registre d'Adresse pour stocker des données ou inversement.
Le modèle de programmation des MCF est très souple. Ceci dit, un fonctionnement optimal requiert l'observation de règles de bon sens : Les adresses avec les adresses. Les données avec les données.

II-B-3 - Les registres de contrôle

Pour finir le tour des registres nous verrons les trois registres de contrôles les plus basiques :

- Le program counter (PC)

- Le status register (SR) ou registre d'état

- Le vector base register (VBR)

Commençons par le Program counter (PC). C'est un registre de 32 bits qui contient l'adresse du premier mot de l'instruction suivante. Il s'incrémente automatiquement.
C'est sur lui qu'agiront les instructions de branchement.

Chacun des 16 bits du registre d'état (Status register) a une fonction particulière. Les valeurs sont modifiées automatiquement par certaines instructions. La partie basse, le Condition Code Register (CCR) permet à l'utilisateur d'effectuer des tests, de récupérer une retenue ... La partie haute est réservée au superviseur.

Partie superviseur :

- T : 1 mode Trace (pour l'exécution pas à pas), 0 mode normal

- S : 1 mode Superviseur (par défaut), 0 mode Utilisateur

- M : mis à 0 par un départ en interruption

- I : masque d'interruption

Partie utilisateur :

- C et X sont des retenues. Contrairement à C, X n'est pas affecté par les comparaisons

- N négatif : positionné comme le poids fort du résultat

- Z indicateur de Zéro (Attention : vaut 1 si résultat vaut 0)

- V débordement

Le troisième registre auquel nous nous intéressons est le VBR (32 bits) qui contient l'adresse de base de la table des vecteurs. Lors d'un Reset le VBR est Forcé à 0. Les 20 bits de poids faible valent 0 afin que la table des vecteurs soit toujours alignée sur une adresse 0-modulo-1.

Lors d'une erreur ou lorsqu'un périphérique signale un événement, il se produit une exception. Chaque exception (erreur ou interruption) est associée à un numéro de vecteur. Ce numéro permet de retrouver l'adresse du programme à exécuter pour traiter l'exception.

III - Modèle de programmation

III-A - Syntaxe

Avant d'aller plus loin, il est nécessaire de présenter rapidement la syntaxe Motorola. Ceux qui ont déjà pratiqué l'assembleur savent que chaque constructeur a sa syntaxe propre qui est de plus susceptible de varier suivant le compilateur utilisé. Pour plus de détails, il faudra se référer à la documentation du programme d'assemblage choisi.

- Les mnémoniques sont suffixées par la taille de l'opérande

- La source précède la destination.

MOVE.L D0, D1 ; Place le mot  long (32 bits) contenu dans D0 dans D1
MOVE.W D0, D1 ; Place le mot (16 bits de poids faible) contenu dans D0 dans D1
MOVE.B D0, D1 ; Place l'octet (8 bits de poids faible) contenu dans D0 dans D1

- # est l'opérateur d'immédiateté

- $ désigne un nombre hexadécimal

- % désigne un nombre binaire

MOVE.B #10,D0 ; place 10 (décimale) dans D0
MOVE.B #$10,D0 ; place 0x10 (hexadécimale) dans D0
MOVE.B #,D0 ; place 0b10 (binaire) dans D0
MOVE.B 10,D0 ; place ce qui est à l'adresse 10 dans D0

Les labels commencent en colonne 0 par une lettre. Ils terminent par " : " et contiennent uniquement des caractères alphanumériques ainsi que l'underscore " _ ". Ils sont sensibles à la casse.

label:  nop
bra label

Certains assembleurs (pas très malins) exigent de suffixer la source pour lever l'ambiguïté entre différents opcodes. Cela n'a rien à voir avec la taille de l'opérande.

MULU.W D0.L,D1

III-B - Modes d'adressage

Avant d'attaquer les instructions attardons-nous sur les modes d'adressages. Ils sont au nombre de onze. Leur choix détermine souvent le succès d'un développement.

Absolu

MOVE.L $10,D0

Place ce qui est à l'adresse 10 dans D0

Immédiat

MOVE.L #$10,D0

Place la valeur 10 dans D0

Direct registre d'adresse

MOVEA.L A0,A1

Place ce qui est dans le registre A0 dans le registre A1

Direct registre de donnée

MOVE.L D0,D1

Place ce qui est dans D0 dans D1

Indirect registre d'adresse

MOVE.L (A0),D0

Place ce qui est désigné par l'adresse contenue dans A0 dans D0

Indirect registre d'adresse pré-décrémenté

MOVE.L -(A0),D0

Décrémente A0 de la taille de l'opérande puis comme indirect registre d'adresse

Indirect registre d'adresse post-incrémenté

MOVE.L (A0)+,D0

Comme indirect registre d'adresse puis A0 est incrémenté de la taille de l'opérande

Indirect registre d'adresse avec déplacement

MOVE.L (12,A0),D0

Place dans D0 ce qui est désigné par l'adresse contenue dans A0 +12. Le déplacement est sur 16 bits. L'addition est signée. A0 n'est pas modifié.

Indirect registre d'adresse avec déplacement, index et facteur d'échelle

MOVE.L (12,A0,D0,S)

L'adresse effective est obtenue en additionnant en signé les 32 bits de A0 au déplacement (ici 12) puis à l'index (D0) multiplié par le facteur d'échelle (S). S peut être égal à 1, 2 ou 4. A0 n'est pas modifié

Relatif

BRA $2A

On ajoutera 0x2A en signé au Program Counter. Le déplacement est sur 16 bits signé. Généralement on utilise un label.

Relatif compteur programme avec déplacement et relatif compteur programme avec déplacement, index et facteur d'échelle.

MOVE.W ($14,PC) MOVE.W ($14,PC,D3,S)

Voir indirect registre d'adresse avec déplacement et suivant. Interdit en destination

Pour écrire des modules translatables, il est important de définir une stratégie d'adressage souple. L'utilisation de pointeurs plutôt que d'adresses "dures" est vivement recommandé. On trouvera en annexe toutes les combinaisons possibles entre modes d'adressage et instructions. Le MCF est bien plus contraignant que le 68000 en ce domaine. Il est par exemple impossible d'écrire :

DIVU.W #1000,D3

Il faut passer la constante par un registre

MOVE.L #1000,D2
DIVU.W D2,D3

III-C - Jeu d'instruction

Une instruction est composée d'une mnémonique suffixée et de deux opérandes (une seule dans certains cas). Chaque opérande est elle-même composée d'un couple valeur / mode d'adressage. Lorsque l'instruction modifie une valeur le résultat est toujours placé dans la destination.

Mnémonique

Suffixe

Source

Destination

MOVE

.L

(A0)

D1

La source est A0 en adressage indirect registre d'adresse

La destination est D0 en adressage direct registre de donnée

Certaines instructions ont une incidence sur le CCR et ne supportent pas toutes les tailles d'opérandes. Le détail est fourni en annexe.

L'un des opérateurs est toujours un registre.

On a toujours envie de trouver une façon ludique de présenter les instructions. Malheureusement, je ne vois pas de meilleur moyen pour être relativement concis et exhaustif que d'utiliser des tableaux énumératifs.

Chaque mnémonique présentée correspond à un opcode particulier. Autrement dit, ADD ou ADDQ font tout les deux des additions, mais il s'agit d'instructions totalement différentes d'un point de vue physique. Les câbles utilisés ne sont pas les mêmes. Cela implique qu'il ne faut pas se tromper dans le choix des instructions, si l'on veut des performances optimales. Certains assembleurs sauront choisir entre, par exemple, ADDI, ADDQ ou ADD. Mais ce n'est pas toujours le cas.

Si certaines instructions apparaissent deux fois c'est qu'elles ont un comportement différent suivent les modes d'adressages utilisés.

On peut découper le jeu d'instruction en plusieurs sous catégories : arithmétique, logique, branchement, contrôle, déplacement ... Commençons par les plus populaires :

III-C-1 - Les instructions de déplacement

Instruction

Explication

Exemple

MOVE

Déplace la source dans la destination

MOVE.L D0,D1

MOVEA

La source est transférée dans un registre d'adresse

MOVEA.L D0,A1

MOVEM

MOVE mémoire vers liste de registres et inversement

MOVEM.L DO-7,(A0)
MOVEM.L (A0),D0-7

MOVEQ

Placer une constante de 8bits dans un registre de donnée

MOVEQ.L #3,D0

III-C-2 - Les instructions logiques

Instruction

Explication

Exemple

AND

Source ET Destination

AND.L DO,D1

ANDI

Constante ET destination

ANDI.L #$FF, D1

EOR

Source OU EXCLUSIF Destination

EOR.L DO,D1

EORI

Constante OU EXCLUSIF destination

EORI.L #$FF, D1

OR

Source OU Destination

OR.L DO,D1

ORI

Constante OU destination

ORI.L #$FF, D1

NOT

NON logique

NOT.L D0

III-C-3 - Les instructions arithmétiques

Instruction

Explication

Exemple

ADD

Source + destination

ADD.L D0,D1

ADDA

La destination est une adresse

ADDA.L D0,A1

ADDI

La source est une constante

ADDI.L #$FF,D0

ADDQ

La source est une constante entre 1 et 8

ADDQ.L #1,D0

ADDX

Source + Destination + bit X

ADDX.L D0,D1

SUB

Destination - Source

SUB.L D0,D1

SUBA

La destination est une adresse

SUBA.L D0,A1

SUBI

La source est une constante

SUBI.L #$FF,D0

SUBQ

La source est une constante entre 1 et 8

SUBQ.L #1,D0

SUBX

Source -Destination - bit X

SUBX.L D0,D1

MULU.W

Multiplie les 2 opérandes de 16 bits résultat sur 32 dans la destination

MULU.W D0,D1

MULU.L

Multiplie les 2 opérandes de 32 bits. Poids faibles du résultat dans la destination

MULU.L D0,D1

MULS.W

Comme MULU.W mais en signé

MULU.W D0,D1

MULS.L

Comme MULU.L mais en signé

MULU.L D0,D1

DIVU.W

Divise les 32 bits de la destination par les 16 bits de la source.
Le résultat est dans le mot de poids faible de la destination. Le reste dans le poids fort.

DIVU.W D0,D1

DIVU.L

Divise les 32 bits de la destination par les 32 bits de la source.
Résultat dans les 32 bits de la destination

DIVU.L D0,D1

DIVS.W

Comme DIVU.W mais en signé

DIVS.W D0,D1

DIVS.L

Comme DIVU.L mais en signé

DIVS.L D0,D1

III-C-4 - Décalages et permutations

Instruction

Explication

Exemple

LSL

Décalage logique à gauche.
Constante entre 1 et 8

LSL.L #3,D0
LSL.L D1,D0

LSR

Décalage logique à droite
Constante entre 1 et 8

LSR.L #3,D0

LSR.L D1,D0

ASL

Décalage Arithmétique à gauche.
Constante entre 1 et 8

ASL.L #3,D0

ASL.L D1,D0

ASR

Décalage Arithmétique à droite.
Constante entre 1 et 8. Extension de signe

ASR.L #3,D0

ASR.L D1,D0

CLR

Force la valeur à zéro

CLR.B D0

EXT

Etend le signe d'un registre d'un octet à un mot ou d'un mot à un mot long

EXT.W D0

EXTB

Etend le signe d'un registre d'un octet à un mot long

EXT.L D0

NEG

L'opérande est soustrait à 0

NEG.L D0

NEGX

L'opérande et le bit X sont retranchés à 0

NEGX.L D0

SWAP

Permute le mot de poids fort et le mot de poids faible d'un registre

SWAP.W D0

III-C-5 - Opérations sur les bits

Instruction

Explication

Exemple

BCHG

Le bit est complémenté. La destination est un registre.

Le numéro du bit est modulo 32. La source est une constante

BCHG.L #31,D0

BCHG

Le bit est complémenté. La destination est une adresse.
Le numéro du bit est modulo 8. La source est un registre

BCHG.B D0,$1000

BCLR

Le bit est forcé à zéro La destination est un registre.
Le numéro du bit est modulo 32. La source est une constante

BCLR.L #31,D0

BCLR

Le bit est forcé à zéro La destination est une adresse.
Le numéro du bit est modulo 8. La source est un registre

BCLR.B D0,$1000

BSET

Le bit est forcé à un. La destination est un registre.
Le numéro du bit est modulo 32. La source est une constante

BSET.L #31,D0

BSET

Le bit est testé est forcé à un.
La destination est une adresse. Le numéro du bit est modulo 8.
La source est un registre

BSET.B D0,$1000

BTST

Le bit est testé mais pas modifié.
La destination est un registre. Le numéro du bit est modulo 32.
La source est une constante

BTST.L #31,D0

BTST

Le bit est testé mais pas modifié.
La destination est une adresse. Le numéro du bit est modulo 8.
La source est un registre

BTST.B D0,$1000

III-C-6 - Tests, comparaisons, branchements

Instruction

Explication

Exemple

CMP

Soustrait la source à la destination. Le résultat n'est pas rangé dans la destination. Le mot d'état seul est affecté

CMP.L D0,D1

CMPA

Comme CMP pour les registres d'adresse

CMPA.L,D0,A0

CMPI

Comparaison avec une constante

CMPI.L #$FF,D0

TST

Compare l'opérande à 0

TST.W,D0

Bcc

Voir ci-dessous

BEQ BOUCLE

Scc

Voir ci-dessous

SEQ.B D0

BRA

L'exécution continue au label

BRA BOU

BSR

PC est poussé dans la pile. L'exemption continue au label

BSR MONPROC

RTS

Pousse le sommet de la pile dans PC

RTS

JSR

Comme BSR mais l'adresse est codée en dur

JSR (A0)

JMP

Comme BRA mais l'adresse est codée en dur

JMP (A0)

JSR et JMP sont à éviter si l'on veut faire des modules translatables. Préférer BSR et BRA.

Bcc est une série d'instruction qui, si la condition est vérifiée poursuivent l'exécution à un label précis.
cc peut avoir les valeurs suivantes :

Valeur de cc

Explication

CC ou HS

Retenue à zéro

CS ou LO

Retenue à 1

EQ

Egal

GE

Supérieur ou égal signé

GT

Supérieur signé

HI

Supérieur

LE

Inférieur ou égal signé

LS

Inférieur ou égal

LT

Inférieur signé

MI

Négatif signé

NE

Différent

PL

Positif signé

VC

Pas de débordement signé

VS

Débordement signé

Comme Bcc, Scc est une série d'instructions. Si la condition est vraie, l'octet de la destination est forcé à FF sinon, il est mis à zéro.
cc peut avoir les valeurs suivantes :

Valeur de cc

Explication

CC

Retenue à zéro

CS

Retenue à 1

EQ

Egal

GE

Supérieur ou égal signé

GT

Supérieur signé

HI

Supérieur

LE

Inférieur ou égal signé

LS

Inférieur ou égal

LT

Inférieur signé

MI

Négatif signé

NE

Différent

PL

Positif signé

VC

Pas de débordement signé

VS

Débordement signé

F

Jamais vrai

T

Toujours vrai

III-C-7 - Instructions de contrôle et divers

Série des instructions génériques :

Instruction

Explication

Exemple

LEA

Calcule l'adresse effective et la range dans la destination.

LEA.L (12,A0),A1

PEA

Comme LEA mais pousse le résultat dans la pile

PEA.L (12,A0)

LINK

Voir gestion des variables

ULNK

Voir gestion des variables

NOP

Ne rien faire

NOP

TRAP

Voir gestion des exceptions

MOVE from CCR

Copie le CCR dans la destination

MOVE.WCCR,D0

MOVE to CCR

Copie la source dans le CCR

MOVE.W D0,CCR

Série des instructions privilégiées :

Instruction

Explication

Exemple

MOVE from SR

Copie le registre d'état

MOVE.W SR,D0

MOVE to SR

Copie la source dans le registre d'état

MOVE.W D0,SR

MOVEC

Transfère le contenu d'un registre dans un registre de contrôle.

MOVEC.L D0,VBR

RTE

Restaure depuis la pile SR puis PC.

RTE

STOP

Lance une exception de type RESET

STOP #$AF8E

III-D - Exemples

Pour se familiariser avec le jeu d'instruction des MCF, il serait bon de regarder ces quelques exemples/exercices classiques :

III-D-1 - Le codage BCD

Ce module, donne en code BCD l'équivalent d'un mot de 16 bits Le nombre de départ est dans les 16 bits de poids faible de D0 Le résultat est dans D1.
Exemple : D0 0000FFFF
donne D1 : 00065535
Cela permet de traduire directement le nombre vers un afficheur sans passer par l'ASCII
Algorithme :
Un mot de 16 bit représente une valeur non signé comprise entre 0 et 65535. On a donc cinq chiffres entre 0 et 9 a trouver pour exprimer la valeur en BCD

Entier N //valeur initiale sur 16 bits
Entier r <br/>
<br/>
On divise N par 10000 -> premier chiffre du résultat <br/>
r <- reste de la division         <br/>
On divise r par 1000 -> second chiffre du résultat   <br/>
r <- reste de la division      <br/>
On recommence avec 100 puis 10 -> troisième et quatrième chiffres <br/>
Le reste donne le cinquième chiffre     <br/>
<br/>

La question de la réentrance et de la sauvegarde du contexte sont volontairement ignorées.

org $10000
debut:        nop             ;ne rien faire
andi.L #$FFFF,D0    ;on efface les 16 bits de poids fort de D0
move.L D0,D2        ;copions D0 dans D2
clr.L D1        ;effaçons D1
move.L #10000,D3    ;plaçons 10000 dans D3. La division ne supporte pas l'adressage immédiat
divu.w D3,D2        ;divisons de D2 (sur 32 bits) par D3 (sur 16 bit).
;Le résultat sera dans les 16 bits de poids faible de D2
;Le reste dans les 16 bits de poids fort de D2
move.w D2,D1        ;on place le resultat dans D1
lsl.L #4,D1        ;on décale D1 de 4 bits vers la gauche
swap.w D2        ;on permute les 16 bits de pods faible et les 16 bits de poids fort de D2

fin_1:        nop

Nous avons maintenant, notre première valeur dans la partie supérieure de l'octet de poids faible de D1

D1.B

15

8 7 6 5 4 3 2 1 0

. x x x x . . . .

Passons à la seconde valeur

debut_2:    nop
andi.L #FFFF,D2
move.L #1000,D3        ;Une division par 10 de D3 serait bien plus couteuse
divu.w D3,D2
move.b D2,D3        ;ce qui nous interesse est dans l'octet de poids faible de D2
andi.L #F,D3        ;plus précisement dans les 4 premier bits
or.L D3,D1
lsl.l #4,D1
swap.w D2

fin_2:        nop

Maintenant que nous avons compris le mécanisme Terminons ou plutôt terminez ....... ;-)

faire des variantes sur 32 bits, en signé ...

III-D-2 - Addition de deux mots de 64 bits

Ce module additionne deux mots de 64 bits.
Pour l'instant, nous ne gérons pas les variables. Les opérandes se situerons donc à une adresse mémoire précise. Le résultat sera également placé en mémoire.
Nous réserverons donc un espace de 7x32 bits d'origine mem répartie comme il suit :

label

exemple

opérande

mem

1000

Opérande 1 poids fort

...

1007

Opérande 1 poids faible

1008

Opérande 2 poids fort

...

100F

Opérande 2 poids faible

memOp

1010

...

1013

Retenue dans le bit de poids faible

1014

Résultat poids fort

...

1017

1018

...

101B

Résultat poids faible

memRes

101C

On réserve 32 bits pour stocker la retenue. Pour une question de performance et à moins d'être vraiment contraint par l'espace, c'est la meilleure solution.

*Directives d'assemblages et définition de constantes *

mem_op equ  $20010    ;définition de deux constantes
mem_res equ  $2001C

org $10000    ; adresse à laquelle le programme sera chargé
cpu 5206e    ; cela permet de gérer les versions des MCF
opt optimise=all; On demande au compilateur d'être intelligeant.

*dans cet entête, dans ce cas précis seul org est obligatoire. Le reste est donné à titre d'exemple*
debut:    nop

movea.L #mem_op,A0    ;mise en place des pointeurs
movea.L #mem_res,A1

addi.L #0,D0        ;mise à zéro du bit x

move.L -(A0),D0        ;récupération des poids faibles de l'operande 2
move.L (-8,A0),D1    ;récuperation des poids faibles de l'operande 1
addx.L D0,D1        ;L'utilisation d'addx plustôt que d'add ne se justifiera que plus tard.
;En M68K on aurait pu aller chercher la source directement en mémoire
move.L D1,-(A1)        ;On range les poids faibles du résultat

move.L -(A0),D0        ;Cette sequence est exactement identique à la précedente (d'où l'addx).
move.L (-8,A0),D1    ;Ne pourrait-on pas boucler ? Essayez (attention à la retenue)! est-ce interessant ?
addx.L D0,D1
move.L D1,-(A1)

clr.L D0        ;On efface D0
addx.L D0,D0        ;On récupère la retenue
move.L D0,-(A1)        ;On la range

fin:    nop

III-D-3 - Parcours itératif d'un tableau d'octet

L'objet de ce module, sera de comprendre certains travers liés à l'absence de maîtrise du jeu d'instruction. Le but du jeu sera donc de comprendre en quoi ce code est nul et de l'améliorer.

Le tableau sera désigné par une adresse de départ dans A0 et un offset dans D0. La valeur recherchée sera dans D1.b Le résultat sera un pointeur placé dans A1.

org $10000

debut :        nop

move.L D0,D2        ; sauvegardons l'offset de réference

bou:        cmp.B (A0,D2),D1    ;La valeur pointé par A0 + offest est-elle la valeur cherchée ?
beq trouve        ;Si oui on va à trouve
subi.L #1,D2        ;Sinon on décrémente l'offset
bne no_value        ;Si l'offset est négatif on a pas trouvé
bra bou            ;Sinon on recommence

no_value:    clr.L A1        ;Si on n'a rien trouvé, on renvera un pointeur = 0
bra fin            ;et on termine

trouve:        movea.L A0,A1        ;Si on a trouvé, on calcule l'adresse du pointeur
adda.L D2,A1

fin:         nop

Il faudrait regarder un peu du coté de l'instruction Scc, de LEA... Puis on doit pouvoir ne pas utiliser de BRA.

IV - Gestion du contexte et des variables

IV-A - Gestion de la pile

La première utilisation que l'on fait de la pile système est la sauvegarde du contexte. En effet, lorsqu'on écrit un module en assembleur, la maîtrise de son contexte d'exécution est toujours imparfaite. Les mécanismes mis en oeuvre dans une machine sont tellement complexes qu'il est impossible dans la plus part des cas de déterminer avec certitude l'ensemble des cas de passage d'un module à un autre. Cela est vrai lorsqu'on travaille en temps partagé, mais aussi en mono tâche à cause des départs en interruption. De plus pour pouvoir réutiliser et maintenir facilement du code, il faut écrire propre.

Dans un module, on va utiliser un certain nombre de registres qu'il faudra impérativement rendre dans leur état initial : sauver le contexte.

Comme nous l'avons dit la pile système est définie par le pointeur dans A7. Pour mémoire une pile est une zone désignée par un pointeur dans laquelle on ajoute les éléments selon un algorithme LIFO (Last In First Out). Le pointeur de pile désigne toujours le dernier emplacement occupé.

- Décrémenter le pointeur

- Pousser l'élément

- Retirer l'élément

- Incrémenter le pointeur

A7 est parfois appelé SP pour stack pointer

Les ColdFire n'ont pas d'instructions PUSH et POP. Par contre, on se rend vite compte que les modes d'adressage indirect registre d'adresse pré décrémenté et post-incrémentés conviennent parfaitement à cet usage.

Imaginons un module dans lequel on utilise A0, A1, D0, D1 . La première chose à faire en début de module, est de sauver le contenu de ces quatre registres. Il faudra les restaurer en sortant. à la fin.

MOVE.L A0, -(A7)
MOVE.L A1, -(A7)
MOVE.L D0, -(A7)
MOVE.L D1, -(A7)
* Code du module ici
* ...
* Restauration du contexte
* Attention à l'ordre *
MOVE.L (A7)+,D1
MOVE.L (A7)+,D0
MOVEA.L (A7)+,A1
MOVEA.L (A7)+,A0

Avec ce système, quel que soit le contexte, on sort toujours proprement d'un module.

Une autre solution consiste à utiliser les instructions LEA et MOVEM. Cela est plus rapide lorsqu'il y a une grande quantité de registres à sauvegarder. Par exemple, sauvons tous les registres de donnée et deux registres d'adresse :

LEA.L (-40,A7),A7 ; on décrémante le pointeur de pile de 8*4+2*4 octets
MOVEM.L A0/A1/D0-D7,(A7)
* Code du module ici
* ...
MOVEM.L (A7),A0/A1/D0-D7 LEA.L (40,A7),A7 ;
on restaure le pointeur de pile

La pile doit être gérée avec la plus grande rigueur. Tout ce qui a été empilé doit être dépilé. Les erreurs en ce domaine peuvent passer inaperçues. Pour ne provoquer des catastrophes qu'une fois en production. L'exemple type est une machine fonctionnant sans arrêt. Si on oublie de dépiler 1 mot long dans un module utilisé 10 fois par jour, on décale le pointeur de pile d'environ 14 ko par an. Un jour au bout de quelques mois la pile va grignoter la mémoire d'un programme (stack overflow). Bien malheureux celui qui doit identifier le problème.

Réécrire l'ensemble des modules vus de la partie précédente avec sauvegarde du contexte.

IV-B - Départ en sous-programme

On a tendance à abuser des départs en sous programme. Il ne faut jamais perdre de vue que ce n'est pas parce qu'on fait appel deux fois au même bout de code dans un module qu'il est forcément plus judicieux d'en faire un sous programme que de le recopier.
La problématique est la même qu'en langage évolué. Par exemple en C, vaut-il mieux écrire :

#define min(a,b) a>b?b:a

int main(void){
int m = min(50,25);
printf("Le minimum est : %d",m);
return 0;
}

Ou :

int min(int a, int b){return a>b?b:a;}

int main(void){
int m = min(50,25);
printf("Le minimum est : %d",m);
return 0;
}

Cela est une question d'appréciation. On doit toujours se poser la question du coût d'une telle mécanique avant de la mettre en oeuvre.

Un sous programme sera appelé grâce à l'instruction BSR. Un RTS reviendra vers l'appelant. BSR effectue un branchement en poussant l'adresse de retour sur la pile. RTS récupère cette adresse et la force dans PC.
Reprenons l'exemple de l'addition et utilisons un sous programme :

org $10000

memOp equ $20010
memRes equ $2001C

ss_prog :          nop

move.L -(A0),D0
move.L (-8, A0),D1
addx.L D0,D1
move.L D1,-(A1)

fin_ss_prog :     rts

debut:  nop
move.L A0, -(A7) ;sauvegarde du contexte
move.L A1, -(A7)
move.L D0, -(A7)
move.L  D1, -(A7)

movea.L #$memOp,A0 ;mise en place des pointeurs
movea.L #$memRes,A1

addi.L #0,D0   ;mise à zéro du bit x

bsr ss_prog
bsr ss_prog

clr.L D0

addx.L D0,D0
move.L D0,-(A1)

move.L (A7)+,D1 ;restauration du contexte
move.L (A7)+,D0
movea.L (A7)+,A1
movea.L (A7)+,A0
fin:      nop

On voit que, dans cet exemple, le choix d'un départ en sous programme n'est pas du tout judicieux puisqu'il ajoute quatre accès mémoire pour gagner quatre lignes de code. Par contre, si l'on faisait des additions sur 64 octets ça serait bien plus intéressant.

BSR utilise l'adressage relatif. Le déplacement étant sur 16 bits signés, la portée du branchement est donc limitée. On pourra utiliser JSR en cas de dépassement.

IV-C - Gestion des variables

Supposons qu'on utilise le module d'addition précédent dans un programme. Celui-ci se déroule sans anicroche. Tout à coup, un départ en exception interrompt mon programme. Le traitement de l'exception utilisant mon module d'addition, les valeurs en mémoire (opérandes 1 et 2 et résultat) sont irrémédiablement écrasés. Du coup ma machine devient incohérente. C'est le problème de la réentrance des modules, pour lequel, la solution la plus commune est de travailler comme un compilateur.

En langage C une fonction se caractérise par une valeur de retour et des paramètres : int min(int a, int b)
Sur une machine où l'entier fait 16 bits, ce prototype revient à réserver un espace d'échange de 3x16 bits entre la fonction appelante et la fonction appelée. En C rien n'empêche également de déclarer 10, 20, ou 30 variables de type long int dans une fonction, ou encore une chaine de 200 caractères, ou un tableau 2000 pointeurs ?
On voit bien que nos 8 registres de donnée ne sauraient être suffisants dans tout les cas.

Le moyen le plus rapide de passer des paramètres et des valeurs de retour reste les registres. Lorsque cela est possible, cette méthode dont la rapidité n'a aucune commune mesure avec les accès mémoires de la pile, doit être privilégiée.

D'une manière générale, on peut distinguer trois types de variables :

- Les variables internes. Elles ne concernent qu'un module donné.

- Les variables externes. Qui sont les paramètres et les valeurs de retour.

- Les variables globales qui sont communes à plusieurs modules.

Pour gérer les variables, les compilateurs utilisent l'instruction LINK. Nous ferons de même. L'instruction LINK a pour but de créer des zones dans la pile pour stocker des variables. On l'utilise de la façon suivante :

LINK Ax,#-déplacement

Généralement par convention c'est A6 qui est utilisé. Il va sans dire que si on utilise ce système, A6 ne doit plus être touché.

Que se passe-t-il lorsqu'on fait un LINK A6 #-4 ?

LINK utilise toujours des opérandes d'un mot.

- la valeur d'A6 est poussée dans la pile.

- la valeur de A7 est copiée dans A6.

- Le déplacement sur 16 bits sera étendu sur 32 bits.

- Le déplacement (négatif) sera ajouté à A7.

- A7 sera donc décrémenté de 4 mots.

On aura donc dans notre module accès à notre zone de variable en utilisant A7 et le déplacement positif approprié.

Si on change de module, et que le nouveau module commence par un link, nous aurons accès à la zone de variable du premier module par A6 et un déplacement positif. (il faudra tenir compte de ce qui aura été empilé entre temps. Comme par exemple l'adresse de retour.)

Il faudra faire un ULNK A6 à chaque sortie de module.

L'instruction LINK permet donc de gérer les variables internes et externes d'un module. Si par contre on n'a besoin que de variables internes, on peut se contenter d'utiliser LEA qui est une instruction bien moins gourmande.
Par exemple :

LEA.L (-6,A7),A7

réservera une zone de 3 mots dans la pile.
En sortie de module :

LEA.L (6,A7),A7

remettra tout en place.

Enfin pour les variables globales et les constantes, on peut utiliser des adresses fixes (pas de réentrance, pas translatable), ou un autre pointeur sur la pile (par exemple A5).

La gestion des variables n'est pas une mince affaire. Il faut absolument être méthodique : ne pas hésiter à faire des schémas et utiliser des noms explicites.
La programmation en assembleur permet de ne pas être aussi systématique qu'un compilateur. Il faut savoir profiter de cette souplesse. Toutefois les modules doivent rester réentrant et translatables.

Pour mieux appréhender la gestion des variables, regardons le squelette d'un module utilisant deux variables de 32 bits en interne. Il appellera un second module en passant une variable de 16 bits en paramètre. Le second module utilisera quand à lui une variable interne de 32 bits

*********************************************************
*                            *
* Définition des pointeurs                 *
*                            *
* * Etape 1 (e1) : on réserve 3x32 bits pour module 1     *
*    (laissons la pile alignée sur un multiple de 4)    *
* * Etape 2 (e2) : on accède aux 3 variables du module 1*
* * Etape 3 (e3) on part en sous programme        *
* * Etape 4 (e4) on réserve 32 bits pour module 2    *
* * Etape 5 (e5) on accède aux variables depuis module 2*
*********************************************************
* Pile     e0  e1  e2      e3  e4  e5
* 3FFE1
* 3FFE2
* 3FFE3
* 3FFE4<---------------------A7--(A7)
* 3FFE5                            |
* 3FFE6                            |
* 3FFE7-----------------------------
* 3FFE8<---------------------A6--(8,A6)
* 3FFE9                              |
* 3FFEA                              |
* 3FFEB                              |
* 3FFEC<-----------------A7          |
* 3FFED                              |
* 3FFEE                              |
* 3FFEF                              |
* 3FFF0<-----A7--(A7)<----------------
* 3FFF1            |                 |
* 3FFF2            |                 |
* 3FFF3-------------------------------
* 3FFF4<---------(4,A7)
* 3FFF5              |
* 3FFF6              |
* 3FFF7---------------
* 3FFF8<---------(8,A7)
* 3FFF9              |
* 3FFFA              |
* 3FFFB---------------
* 3FFFC<-----A6
* 3FFFD
* 3FFFE
* 3FFFF
* 40000<-A7

Faire de tels schémas est souvent la meilleure solution pour s'y retrouver.

org $20000

mod1:    nop

link a6,#-6 ;étape 1 à noter que link est en .w

;Faire quelque chose avec ces variables
move.l (8,A7),D0
move.l (4,A7),D0

;Initialisation de la variable externe
move.w #$BBBB,D0
move.w D0,(A7)

;départ en sous programme
bsr mod2

;liberer la pile
unlk a6

fin1:    nop

mod2:     nop

link a6,#-2

;récuperer la variable externe
move.w (8,a6),D1 ; Oh ! miracle BBBB.w apparait dans D1 :)

;faire des choses
;...
;puis partir
unlk a6
fin2:    rts

Pour se familiariser avec la gestion des variables on pourra également traduire en module réentrant les exemples vus précédemment. Une bonne idée enfin est de regarder le code assembleur résultant de la compilation d'un programme en C (Option -S de gcc).

V - Gestion des exceptions

V-A - Mécanisme général

Il existe 256 exceptions possibles sur les ColdFire, dont une dizaine sont réservées à un usage interne. Qu'il s'agisse d'interruptions réservées à des périphériques ou de mécanismes d'erreur, leur déroulement est toujours semblable.

Un départ en exception ne se produit jamais en cours d'exécution d'une instruction. Le processeur finit toujours le traitement en cours avant de commuter le contexte.

- Le registre d'état est sauvegardé dans un registre interne. Il est en suite modifié. Le bit T (mode Trace) est forcé à 0, le bit S est forcé à 1. Une exception se déroule donc toujours en mode superviseur. Le masque d'interruption est relevé au niveau de la demande. Tout interruption de niveau inférieur ou égal sera donc masquée.

- Un numéro de vecteur sur 8 bits est associé à la demande.

- Le Compteur Programme (l'adresse de retour) ainsi que le registre d'état précédemment sauvegardé, sont poussés dans la pile qui est automatiquement alignée

- Le numéro de vecteur est multiplié par quatre et ajouté au VBR. C'est l'adresse du vecteur. Celui-ci est récupéré et poussé dans le PC.

Les interruptions de niveau 7 ne sont pas masquables.

V-B - Reset

Reset est une interruption non masquable provoquée par la mise en état bas de RSTI ou l'instruction STOP.

Quoique son nom ne le laisse pas penser, reset est l'interruption d'initialisation du processeur.

Toute activité est interrompue. Il n'y a aucune sauvegarde du contexte. Le processeur passe en mode superviseur. Le VBR est forcé à 0. Le vecteur 0 est transféré dans A7 ; le vecteur dans PC. On imagine donc bien que les adresses basses (depuis 0) seront définies en ROM et contiendront le code d'initialisation.

Lorsqu'un processeur est initialisé, les registres et la mémoire ne contiennent pas rien mais n'importe quoi !

V-C - Les erreurs

Vecteur

Nom

Description

2

Erreur d'accès

Accès à une zone mémoire protégée

3

Erreur d'adresse

Le processeur tente d'accéder à une instruction sur une adresse impaire

4

Instruction interdite

Le processeur ne connaît pas l'opcode qu'on essaye de lui refiler

5

Division par zéro

8

Violation de privilèges

le processeur est en mode utilisateur et tente d'exécuter une instruction privilégiée.

14

Erreur format

Se produit lorsqu'on a malencontreusement bidouillé la pile et que l'instruction RTE ne retrouve plus ses petits.

Bien souvent on se dit : "division par zéro ! Moi jamais ..." Et puis un jour la machine plante :-\
Tous les vecteurs d'erreur doivent être initialisés.

V-D - Les périphériques

Le gros des exceptions correspond à ce qu'on appelle des interruptions. Cela permet de dialoguer avec les périphériques sans perdre de temps dans des boucles d'attente et autres scans approximatifs.

Vecteur

Nom

Description

25 à 31

Auto vecteurs

Certains périphériques anciens ne peuvent fournir leur numéro de vecteur. Ces interruptions leurs sont réservées.

64 à 255

Vecteurs interruptions utilisateur

Pour les périphériques à même de fournir leur numéro de vecteur.

15

Périphérique non initialisé

15 est la valeur par défaut forcée par l'instruction RESET dans le registre des périphériques donnant le vecteur. Utile pour l'initialisation.

24

Interruption parasite

Si un périphérique ne fournit pas son numéro de vecteur.

Nous n'irons pas plus loin sur les périphériques Ce n'est pas notre propos ici.

V-E - Les exceptions programmables

Il est utile de pouvoir déclencher des départs en interruption, pour une raison ou pour une autre. C'est par exemple le seul moyen de revenir en mode superviseur.

Vecteur

Nom

Description

32 à 47

Vecteur d'instruction TRAP

Voir l'exemple ci-dessous

10 et 11

Emulateur de ligne 1010 et émulateur de ligne 1011

Permet d'émuler des instructions qui n'existent pas. L'opcode doit commencer par 1010 ou 1011. Permet par exemple la compatibilité avec le code M68K. Très lent.

Pour illustrer notre propos, regardons l'utilisation de l'instruction TRAP pour revenir en mode superviseur lorsqu'on est utilisateur et inversement.

org $20000

* Module d'initatlisation du VBR *

debut:     nop

;On va recopier la table des vecteurs à l'adresse 0x40000

clr.l D0
move.b #255,D0 ;On place la valeur 255 dans D0
movea.l #$5000,A0 ;On place l'origine de la nouvelle table dans D1

boucle: move.l D0,D1 ;Copions D0 dans D1
lsl.l #$2,D1 ;On multiplie D1 par 4
movea.l D1,A1 ;On obtient l'adresse du vecteur que l'on place dans A1
move.l (A1),D2 ;On recopie le vecteur dans D2
move.l D2,(A0,D1) ;Puis en mémoire dans la nouvelle table
subq.l #1,D0 ;On décremante le numéro de vecteur
fin_b:    bpl boucle ; Tant que ce n'est pas négatif, on continue.

;On veut maintenant que le vecteur 32 soit la case mémoire 0x5080 point vers l'adresse 0x30000

move.l #$30000,D0
move.l D0,$5080

;Le VBR doit pointer vers 5000

move.l #$5000,D0
movec.l D0,VBR
fin: nop

*********************************************************************************

* Programme de test *

test:    trap #0
bsr test

**********************************************************************************

* On crée le programme associé à l'instruction trap #0 *

org $30000

* Il faut manipuler les valeurs enpilées. On sais qu'un départ en exception empile ceci :
* 16 bits de controles
* 16 bits du SR
* 32 bits du PC

mod_t:    bchg.b #5,(2,SP) ; trifouillons la pile
rte ; obligatoire pour revenir d'exception

VI - L'unité MAC

L'un des champs d'application privilégiés des ColdFire est le traitement de signaux. Qu'il s'agisse de réseau, d'audio, de vidéo, d'images ou toute autre chose, on a très souvent besoin de traiter des signaux analogiques pour en faire des données numériques. Un certain nombre d'opérations mathématiques dites transformées sont alors nécessaires. On aura tous entendu parler de la Transformée de Fourier ou de la Transformée Cosinus Discrète. Ces traitements nécessitent toujours des opérations de la forme :
Somme des f(x) pour x = 0 à x = n

Tous les ColdFire sont doté d'une unité Multiply-Accumulate (dite unité MAC ou eMAC suivant les modèles), dédiée à ces traitements. Incluses sous forme de coprocesseur, leur jeu d'instruction s'utilise comme celui du processeur central.

Nous ne parlerons ici que de l'unité MAC du MCF5307 mais les autres ne présentent pas de grandes différences.

Cette unité présente l'intérêt d'être très rapide. Par exemple, le temps d'exécution de MAC.W est d'un seul cycle, MAC.L prend trois cycles (hors temps d'accès mémoire). Cette unité permet donc le traitement de flux dans de très bonnes conditions.
L'unité MAC réalise les opérations suivantes :

- MAC : a = a + b*c

- MSAC : a = a - b*c

La multiplication peut être agrémentée d'un décalage d'un bit à droite ou à gauche.

L'unité MAC met en œuvre trois registres :

- L'accumulateur (RACC) : un registre de 32 bits contenant le résultat.

- Le Mask Register (RMASK) : un registre de 16 bits.

- Le Registre d'état (MACSR) : un registre de 32 bits dont seul les 8 bits de poids faible sont accessibles

Les déplacements suivants sont possibles :

Instruction

Effet

MOVE.L Dx,RACC

Place le contenu dun registre de donnée dans laccumulateur

MOVE.L #cte,RACC

Place une constante dans laccumulateur

MOVE.L RACC,Dx

Place le contenu de laccumulateur dans un registre de donnée

MOVE.L Dx,MACSR

Place le contenu dun registre de donnée dans le registre détat

MOVE.L #cte,MACSR

Place une constante dans le registre détat

MOVE.L MACSR,CCR

Place le registre détat MAC dans le registre détat principal

MOVE.L MACSR,Dx

Place le registre détat dans un registre

MOVE.L Dx,RMASK

Copie le registre de donnée dans le masque

MOVE.L #cte,RMASK

Copie une constante dans le masque

MOVE.L RMASK, Dx

Copie le masque dans un registre de donnée

Passons aux instructions MAC et MSAC. Leur utilisation est symétrique. Elles existent sous plusieurs formes ( pour Rx tous les registres de donnée et d'adresse sont possibles):

Instruction

Effet

Exemple

Temps d'exécution (Cycles)

MAC|MSAC.L|W Rx,Rx{<<|>>}

Multiplie source par destination. Le résultat est éventuellement décalé. Et ajouté à l'accumulateur

MAC.L D0,D1<<
MAC.L A0,D2

1 pour W , 3 pour L

MAC|MSAC.W Rx.U,Rx.L{<<|>>}

Multiplie les 16 bits de poids fort de la source par les 16 bits de poids faible de la destination. Le résultat est décalé et ajouté à l'accumulateur

MAC.W D0.U,D0.L>> MAC.W D0.U,D1.L

1

MAC|MSAC.W Rx.U,Rx.L {<<|>>},(Ax)+,Rx

Multiplie les 16 bits de poids fort par les 16 bits de poids faible du registre. Le résultat est décalé et ajouté à l'accumulateur. En même temps, la valeur pointée par (Ax)+ est placée dans Rx

MSAC.W D0.U,D0.L,(A1)+,D0

2

Dans la dernière forme l'usage du masque est nécessaire. Celui-ci détermine la façon dont doit boucler le programme.

- RMASK : 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1

- Instruction : MAC.W D0.U,D0.L,(A1)+,D0

Lorsque A1 aura été incrémenté 2 puissance 5 fois (32), il reprendra sa valeur initiale.

Pour utiliser les instructions de branchement de type Bcc, il faudra recopier le registre d'état MAC dans le registre d'état principal :
MOVE.L MACSR,CCR
La correspondance est la suivante :

- CCR : - - - X N Z V C

- MACSR : OMC S/U 0 0 N Z V C

Tous les tests portant sur N Z V C à savoir : MI, PL, EQ, NE, VC, VS peuvent donc être utilisé.

Le bit OMC (Overflow/Saturation Mode Control) influera sur le comportement en fonction du bit S /U. Il évite de faire le tour.
S/U (Signed/Unsigned) permettra le comportement suivant :

- 0 = arithmétique signée. Si OMC est à 1 en cas de débordement positif RACC restera à 0x7FFFFFFF et à 0x80000000 pour un débordement négatif

- 1 = arithmétique non signée. Si OMC est à 1 en cas de débordement RACC saturera à 0x00000000 ou à 0xFFFFFFFF

L'exemple typique de mise en œuvre de l'unité MAC est la création d'un filtre à réponse impulsionnelle finie très répandu dans le domaine du traitement du son. Il s'agit d'un moyen de " lisser " un signal. On multiplie une suite de valeurs issue d'une acquisition analogique par des cœfficients particuliers. On additionne ensuite les valeurs traitées qu'on restitue le plus souvent sous la forme d'un nouveau signal (par exemple dans un équaliseur).
Ici les valeurs seront stockées dans un tableau pointé par A1. Le coefficient sera dans les 16 bits de poids fort (.U), la donnée dans les 16 bits de poids faible. On utilisera D0 comme tampon et D2 pour retourner le résultat .

org $20000

init:    nop
clr.l D0
clr.l D1; D1 sera notrre compteur de boucle
move.l #$FFEF,rmask ; notre tableau fait 32 mots longs
move.l #$FFFFFFC0,macsr ; on travaille en non signé avec
; gestion du débordement.
; les F ne servent à rien (zone protégée).

debut:    clr.l D2
move.l #32,D1
move.l #0,racc ; mise à zero de l'accumulateur
move.l (A2)+,D1 ; mise en place de la premiere valeur

boucle:    mac.w D0.U,D0.L(A0)+,D0
subi.l #1,D1
bne boucle

result: move.l racc,D2
*ici on va chercher d'autres valeurs ...*
bra debut

fin:     nop

Il ne faut pas pour autant négliger l'utilisation de cette unité dans des situations moins évidentes. Le gain de performance est parfois non négligeable. Pour des choses telles que le schéma de Horner ou les calculs de puissance, il est bon de se poser la question.

VII - Conclusion

Il reste bien des choses à voir pour maîtriser pleinement les Motorola ColdFire, gestion de la mémoire et des périphériques en tête. Mais tout cela s'acquiert sans problème avec la documentation au fur et à mesure des besoins et fera sans doute l'objet d'un prochain article. Pour aller plus loin, ne pas hésiter à consulter le site de freescale. On pourra également s'imprégner de l'excellent code d'initialisation de John Cunningham.
En tout état de cause, j'espère avoir fourni les éléments pour aborder concrètement la programmation des puces Motorola.

Pour terminer de façon ludique: installer l'excellent émulateur de David Grant.
Télécharger également le fichier hello présent dans les exemples puis ouvrir deux consoles. Dans la première, lancer l'émulateur. Avec la seconde faire un telnet sur le port 5206 de la machine ("telnet localhost 5206"). Dans l'émulateur, charger le fichier hello (dl hello). Puis lancer le programme (go 20000).
C'est beau non ;-)

Annexes

Récapitulatif Instructions Adressage

Code condition des instructions :

Instruction

C

V

Z

N

X

ADD

X

X

X

X

X

ADDA

ADDI

X

X

X

X

X

ADDQ

X

X

X

X

X

ADDX

X

X

X

X

X

AND

X

X

X

X

ANDI

X

X

X

X

X

ASL

X

X

X

X

X

ASR

X

X

X

X

X

Bcc

BCHG

X

BCLR

X

BRA

BSET

X

BSR

BTST

X

CLR

X

X

X

X

CMP

X

X

X

X

CMPA

X

X

X

X

CMPI

X

X

X

X

DIVS

X

X

X

X

DIVU

X

X

X

X

EOR

X

X

X

X

EORI

X

X

X

X

EXT

X

X

X

X

EXTB

X

X

X

X

JMP

JSR

LEA

LINK

LSL

X

X

X

X

X

LSR

X

X

X

X

X

MOVE

X

X

X

X

MOVE from CCR

MOVE from SR

MOVE to CCR

X

X

X

X

X

MOVE to SR

X

X

X

X

X

MOVEA

MOVEC

MOVEM

MOVEQ

X

X

X

X

MULS

X

X

X

X

MULU

X

X

X

X

NEG

X

X

X

X

X

NEGX

X

X

X

X

X

NOP

NOT

X

X

X

X

OR

X

X

X

X

ORI

X

X

X

X

PEA

RTE

X

X

X

X

X

RTS

Scc

STOP

X

X

X

X

X

SUB

X

X

X

X

X

SUBA

SUBI

X

X

X

X

X

SUBQ

X

X

X

X

X

SUBX

X

X

X

X

X

SWAP

X

X

X

X

TRAP

TST

X

X

X

X

ULNK

Instructions et taille d'opérande :

Instruction

B

W

L

ADD

X

ADDA

X

ADDI

X

ADDQ

X

ADDX

X

AND

X

ANDI

X

ASL

X

ASR

X

Bcc

BCHG

X

X

BCLR

X

X

BRA

BSET

X

X

BSR

BTST

X

X

CLR

X

X

X

CMP

X

CMPA

X

CMPI

X

DIVS

X

X

DIVU

X

X

EOR

X

EORI

X

EXT

X

X

EXTB

X

JMP

JSR

LEA

X

LINK

LSL

X

LSR

X

MOVE

X

X

X

MOVE from CCR

X

MOVE from SR

X

MOVE to CCR

X

MOVE to SR

X

MOVEA

X

X

MOVEC

X

MOVEM

X

MOVEQ

X

MULS

X

X

MULU

X

X

NEG

X

NEGX

X

NOP

NOT

X

OR

X

ORI

X

PEA

X

RTE

RTS

Scc

X

STOP

SUB

X

SUBA

X

SUBI

X

SUBQ

X

SUBX

X

SWAP

X

TRAP

TST

X

X

X

ULNK

Instructions et adressages :

Mode d'adressage

Catégorie 1

Catégorie 2

Catégorie 3

Catégorie 4

Absolu

X

X

Immédiat

X

X

X

Direct registre d'adresse

X

Direct registre de donnée

X

X

Indirect registre d'adresse

X

X

X

X

Indirect registre d'adresse pré-décrémenté

X

X

X

Indirect registre d'adresse post-incrémenté

X

X

X

Indirect registre d'adresse avec déplacement et déplacement et index

X

X

X

X

Instruction

Source

Destination

ADD SUB

Tous

Direct registre de donnée

ADD SUB

Direct registre de donnée

Catégories 2 et 4

ADDA SUBA

Tous

Direct registre d'adresse

ADDI SUBI

Immédiat

Direct registre de donnée

ADDQ SUBQ

Immédiat

Catégorie 4

ADDX SUBX

Direct registre de donnée

Direct registre de donnée

AND

Catégorie 1

Direct registre de donnée

AND

Direct registre de donnée

Catégories 2 et 4

ANDI

Immédiat

Direct registre de donnée

ASL et ASR

Immédiat

Direct registre de donnée

ASL et ASR

Direct registre de donnée

Direct registre de donnée

BCHG BCLR BSET BTSTS

Immédiat

Catégories 1 et 4

BCHG BCLR BSET BTSTS

Direct registre de donnée

Catégories 1 et 4

CLR

Catégories 1 et 4

CMP

Tous

Direct registre de donnée

CMPA

Tous

Direct registre d'adresse

CMPI

Immédiat

Direct registre de donnée

DIVS DIVU MULS MULU

Catégorie 1 sauf immédiat et absolue

Direct registre de donnée

EOR

Direct registre de donnée

Catégories 1 et 4

EORI

Immédiat

Direct registre de donnée

EXT EXTB

Direct registre de donnée

Direct registre de donnée

JMP JSR

Catégorie 3

LEA

Catégorie 3

Direct registre d'adresse

LSL LSR

Immédiat Direct registre de donnée

Direct registre de donnée

MOVE

Tous

Catégories 1 et 4

MOVE from CCR MOVE from SR

SR ou CCR

Direct registre de donnée

MOVE to CCR MOVE to SR

Direct registre de donnée

SR ou CCR

MOVEA

Tous

Direct registre d'adresse

MOVEC

Direct registre de donnée

Registre de contrôle

MOVEM

Liste de registre

Indirect registre d'adresse ou indirect registre d'adresse avec déplacement

MOVEM

Indirect registre d'adresse ou indirect registre d'adresse avec déplacement

Liste de registre

MOVEQ

Immédiat

Direct registre de donnée

NEG NEGX NOT

Direct registre de donnée

OR

Catégorie 1

Direct registre de donnée

OR

Direct registre de donnée

Catégories 2 et 4

ORI

Immédiat

Direct registre de donnée

PEA

Catégorie 3

Scc

Catégories 1 et 4

SWAP

Direct registre de donnée

TST

Tous

Simulateurs, compilateurs ...

Pour développer sur un microcontrôleur, rien ne remplacera jamais une carte d'évaluation. Néanmoins nous pouvons arriver à quelque chose de sérieux avec des outils disponibles gratuitement sur Internet.

L'émulateur de David Grant
c'est un logiciel libre sous licence GPL. Son installation ne pose pas de problème particulier.Télécharger, dézipper le répertoire sous Windows. ./configure puis make sous linux. Le binaire s'appelle ColdFire et peut être lancé en spécifiant un modèle (fichier du répertoire boards). Par défaut c'est un 5206e qui est lancé. L'interface est assez similaire au débogueur natif. La liste des commandes s'obtient en tapant "help".

A noter que les deux ports série sont accessibles en faisant un telnet sur les ports 5206 et 5207

L'assembleur de austexsoftware
Cetassembleur est un Freeware disponible en binaire pour Windows. Il fonctionne très bien sous Linux avec wine. Son utilisation est triviale : cfasm monfichier.s produira monfichier.o au format S19. Il est fourni avec un manuel très complet. Il permet de mettre en oeuvre un certain nombre de facilités de codage. Le fait qu'il n'ait pas de licence d'utilisation explicite est néanmoins un frein à son adoption en production. De plus, il n'y a aucune garantie quand à la continuation de son développement.

Motorola utilise un format de binaire très simple le : S19

Gcc
On peut trouver des chaînes de cross compilation pour ColdFire en format binaire sur le site deuclinx . Une fois installés sous linux ou cygwin les binaires sont dans /usr/local/m68k-uclinux-tools. Par défaut les options m5200, m5206e, m528X, m5307 et m5407 sont disponibles. Gcc utilisant la syntaxe AT&T on pourra utiliser le traducteur de Jan Kratochvil mot2as pour convertir les sources assembleur Motorola.
Gcc a l'avantage d'avoir une licence claire et d'être un produit sans conteste suivi et maintenu.

Free Embedded Tools from MicroAPL
MicroAPL fournit un certain nombre d'outils gratuits et fort utiles pour le développement sur ColdFire :

- ColdFire Init : un générateur de code d'initialisation

- PortAsm/68K for ColdFire : un outil permettant de porter du code 68k vers ColdFire

- CF68KLib : une librairie permettant de faire tourner du code 68000 sur ColdFire

On trouvera ensuite tout un tas de produits commerciaux référencés sur le site deFreescale . Le plus célèbre étant l'IDECodeWarrior .

Liens

- L'émulateur de David Grant http://www.slicer.ca/ColdFire/

- Austexsoftware http://www.austexsoftware.com/

- uclinux http://www.uclinux.org/

- le traducteur de Jan Kratochvil http://www.jankratochvil.net/project/mot2as/

- MicroApl http://www.microapl.co.uk

- FreeScale http://www.freescale.com/

- Metrowerks http://www.metrowerks.com/

- Dev'ColdFire http://ColdFire.zewan.com/

- University of Waterloo ColdFire Resource http://sca.uwaterloo.ca/ColdFire/