Академический Документы
Профессиональный Документы
Культура Документы
Technique et informatique
Section Electricité et systèmes de communication
Laboratoire d’informatique technique
Informatique 3
Microordinateur
Programmation assembleur
Interface C / Assembleur
Avant propos
Ce manuscrit est distribué aux étudiants du module « Informatique 3 » de la section Electricité et système de
communication de la Haute école spécialisée bernoise. Il est complété par des exercices, qui sont distribués en
supplément durant le cours, et un projet, qui est réalisé à la fin du semestre.
La première partie du manuscrit traite la structure d’un microordinateur. Cette partie comprend l’organisation de
la CPU et sa connexion avec les composants externes. La seconde partie du manuscrit introduit la
programmation en assembleur. Cette introduction s’effectue avec des exemples, qui se basent sur un
microcontrôleur RISC à 32 bits : le PXA270 de Intel. La dernière partie du manuscrit traite l’interface C –
assembleur, en se référant aux cours de C (modules informatiques 1 et 2).
Toutes les exercices et le projet sont réalisés avec le kit de développement CARME, qui est introduit au
deuxième semestre.
5.1 Introduction........................................................................................................................................... 89
5.2 Aperçu................................................................................................................................................... 89
5.3 .arm, .thumb..................................................................................................................................... 90
5.4 .global, .extern ........................................................................................................................... 91
5.5 .align.................................................................................................................................................. 91
5.5.1 .balign ....................................................................................................................................... 92
5.5.2 .p2align.................................................................................................................................... 93
5.6 .ascii, .asciz ................................................................................................................................ 93
5.7 .byte................................................................................................................................................... 94
5.7.1 .2byte, .hword ........................................................................................................................ 94
5.7.2 .word, .4byte .......................................................................................................................... 95
5.8 .space ................................................................................................................................................ 95
5.9 .include ........................................................................................................................................... 96
5.10 .equ, .set, = ..................................................................................................................................... 96
5.11 .org ..................................................................................................................................................... 97
5.12 .section ........................................................................................................................................... 97
5.13 .end ................................................................................................................................................... 101
5.14 Les opérations assembleur .................................................................................................................. 101
5.15 Les structures de contrôle en assembleur ............................................................................................ 102
5.15.1 L’assemblage conditionnel avec .if ......................................................................................... 102
5.15.2 L’assemblage conditionnel avec .ifdef .................................................................................. 103
5.15.3 L’assemblage conditionnel avec .ifndef ................................................................................. 103
5.16 La définition de macro avec .macro................................................................................................. 103
5.17 Les structures de répétitions en assembleur ........................................................................................ 104
5.17.1 La répétition avec .rept ........................................................................................................... 105
5.17.2 La répétition paramétrée avec .irp........................................................................................... 106
6 Les sous routines..........................................................................................................................................107
6.1 Introduction......................................................................................................................................... 107
6.2 Appel et retour de sous routine ........................................................................................................... 107
6.2.1 Appel de sous routine.................................................................................................................. 107
6.2.2 Appel emboîté de sous routine, pile ............................................................................................ 108
6.3 La sauvegarde du contenu des registres .............................................................................................. 111
6.4 Le transfert des paramètres ................................................................................................................. 112
6.4.1 Le transfert de paramètres à l’aide de registre............................................................................. 112
6.4.2 Le transfert de paramètres avec la pile ........................................................................................ 113
6.5 APCS................................................................................................................................................... 114
6.6 Définition de variable locaux .............................................................................................................. 115
6.7 Sources d’erreurs................................................................................................................................. 116
6.8 Comparaison entre sous routine et macro ........................................................................................... 117
7 Les interruptions et les exceptions ...............................................................................................................118
7.1 Introduction......................................................................................................................................... 118
7.2 Les propriétés des interruptions .......................................................................................................... 119
7.3 La priorité des exceptions et les modes du processeur........................................................................ 119
7.4 Les interruptions ................................................................................................................................. 120
7.5 Le masquage des interruptions............................................................................................................ 121
7.6 Le Tableau des vecteurs d’exception .................................................................................................. 122
7.7 Déroulement d’une requête d’interruption .......................................................................................... 123
7.8 Comportement temporel des interruptions .......................................................................................... 125
7.9 Le contrôleur d’interruption du PXA270 ............................................................................................ 126
7.10 Les variante pour le contrôleur d’interruption..................................................................................... 129
8 Les Structures de contrôle en assembleur ....................................................................................................131
8.1 Introduction......................................................................................................................................... 131
SISD SIMD
Single Instruction, Single Data Single Instruction, Multiple Data
Ordinateur séquentiel Ordinateur vectoriel en pipeline
MISD MIMD
Multiple Instruction, Single Data Multiple Instruction, Multiple Data
Tableau systolique Multiprocesseur et multi ordinateurs
SISD:
Les ordinateurs SISD travaillent de façon séquentielle. Ces
derniers traitent un seul jeu de donnée en fonction d’une seule load A
instruction : load B
t
Single instruction veut dire, qu’une instruction est exécutée par C=A+B
cycle d’horloge. store C
Single Data veut dire, qu’un jeu de donnée est traité par cycle
d’horloge. CPU
Les ordinateurs SISD sont les ordinateurs les plus répandus,
comme par exemple les ordinateurs personnels (PC) ou les
stations de travail.
Toutefois, avec l’introduction des processeurs multi corps, les
systèmes MIMD seront bientôt plus nombreux.
SIMD:
Les ordinateurs SIMD permettent d’exécuter la même instruction
sur plusieurs jeux de données. Ces derniers sont utilisés par load A1 load A2
exemple pour le traitement des images (JPEG, MPEG). load B1 load B2
t
Single Instruction veut dire, que tous les CPU exécutent la même C1 = A1 + B1 C2 = A2 + B2
instruction à un cycle d’horloge donné. store C1 store C2
Multiple Data veut dire, que chaque CPU traite un jeu de donnée
différent. CPU 1 CPU 2
De nombreux microprocesseur, comme par exemple les PowerPC
ou les x86, possèdent des extensions SIMD avec des instructions
supplémentaires. La CPU peut ainsi traiter simultanément
plusieurs jeux de données, en fonction d’une seule instruction.
MISD:
Un seul jeu de donnée est traité simultanément par plusieurs
CPU, avec des instructions différentes. load A1 load A1
Multiple Instruction veut dire, que chaque CPU exécute une load B1 load B1
t
instruction différentes. C1 = A1 + B1 C2 = A1 * B1
Single Data veut dire, que chaque CPU traite le même jeu de store C1 store C2
donnée.
Les ordinateurs MISD ne sont presque plus utilisés aujourd’hui. CPU 1 CPU 2
MIMD:
Aujourd’hui presque tous les ordinateurs multiprocesseurs se
basent sur l’architecture MIMD. load A1 load D
Multiple Instruction veut dire, que chaque CPU exécute une load B1 E = func1(D)
t
instruction différente par cycle d’horloge. Des programmes C1 = A1 + B1 F=D/2
entiers ou des partitions de ces derniers (process, thread, task) store C1 func2()
sont souvent répartis entre les différents CPU.
Multiple Data veut dire, que chaque CPU traite un jeu de données CPU 1 CPU 2
différent.
Mémoire
programme
Mémoire des
CPU données
Périphérie
La mémoire programme, comme son nom l’indique, contient les instructions du programme. Cette dernière est
en générale non volatile, c’est à dire que son contenu n’est pas perdu lorsque l’alimentation est coupée. Quant à
la mémoire des données, elle contient les variables du programme et est en générale volatile. La CPU lit les
instructions dans la mémoire programme et traite (lecture et écriture) les variables, qui sont stockées dans la
mémoire des données ou les composants périphériques, en fonction de ces dernières.
Le goulot de Von Neumann désigne la limitation du transfert des informations entre la CPU et la mémoire
(programme et données). Ce goulot résulte du système de bus unique.
Mémoire des
données
Bus de données
Mémoire du
programme Bus des instructions CPU
Périphérie
L’architecture de Harvard est souvent utilisée dans les DSP (Digitale Signal Processor). L’avantage de cette
architecture réside dans la lecture simultanée des instructions et des données du programme. Cette architecture
est donc plus rapide que celle de Von Neumann, mais elle est également plus complexe, ce qui influence
forcément le prix.
Dans les chapitres suivant nous nous limiterons à l’architecture de Von Neumann.
1.3 La CPU
La CPU (Central Processing Unit) est le coeur de l’ordinateur. Il contrôle l’exécution du programme et traite les
données. La Figure 6 illustre la structure générale de la CPU. Cette dernière varie en fonction du type et du
fabriquant de l’ordinateur.
Interface du bus
Register
Program-Counter
Opérande
Bus interne
Instruction
Unit
ALU
Résultat
L’unité de contrôle est responsable de l’exécution du programme. Il est composé des éléments suivants :
• Instruction Unit (dispositif de commande) : Il interprète et exécute les instructions du programme.
L’unité de calcul est responsable du traitement des données. Il contient les composants suivants :
• ALU (Arithmetic Logical Unit), qui exécute les opérations arithmétiques et logiques. L’ALU n’exécute que
des opérations avec des nombres entiers. Pour des instructions à virgule flottante ou des instructions
mathématiques plus complexes on emplois souvent un FPU (Floating Point Unit). Les opérations typiques
sont les suivantes :
− Les opérations de transfert (chargement et stockage)
− Les opérations logiques (AND, OR, EXOR et NON)
− Les opérations arithmétiques (addition, soustraction, multiplication)
− Comparaison et décision (compare et réalise le saut de programme si la condition est réalisée)
− Les opérations de décalage (shift left, shift right)
• Les registres de données, qui sont destinés aux stockages des opérandes et du résultat des opérations
(accumulateur). Un registre est une case mémoire rapide, qui se trouve à l’intérieure du processeur. Ce
dernier peut être accédé directement, c’est à dire sans système de bus, par la CPU.
Le bus interne relie l’unité de contrôle, celle de calcul et l’interface du bus. L’unité de contrôle gère les
opérations, qui doivent être exécutées dans l’unité de calcul. Cette dernière retourne des informations concernant
son état à l’unité de contrôle. L’interface du bus gère la communication avec des composants externes (mémoire
et périphérie).
Bus de contrôle
Bus d’adresse
CPU
Mémoire Mémoire des Périphérie
programme données
Bus de données
Le bus d’adresse permet de transmettre les adresses (pour la sélection des cases mémoires) Avec qui?
aux composants externes (mémoire et périphérie), qui sont connectés à la CPU. Ces adresses
sont déterminées par l’unité de contrôle de la CPU. Souvent des décodeurs d’adresse
supplémentaire doivent être utilisés, afin de pouvoir sélectionner un des composants externes
(voir chapitre 1.4.3).
Le bus de données permet de transmettre les instructions et les données du programme. Ces quoi?
dernières sont
a) lues par la CPU à partir des composants de stockage externes (RAM ou ROM) ou
périphériques.
b) générées par la CPU et écrites dans les composants de stockage externes ou
périphériques.
Le bus de contrôle permet de transmettre des informations supplémentaires pour la gestion de comment?
la communication (read/write, reset, interrupts, requests et acknowledge, handshake etc.).
Exemple :
Le fonctionnent du système de bus peut être illustré plus en détail, à l’aide d’un transfert de données depuis la
périphérie, en passant par la CPU, à la mémoire des données. Exemple : lecture de la température à l’aide d’une
sonde x (périphérie) et stockage de cette dernière dans la variable "temp", qui se situe dans le RAM. En C cella
serait définie de la manière suivante :
1) La CPU dépose l’adresse de l’instruction à lire sur le bus d’adresse. Cette adresse est stockée dans le
compteur de programme (Program counter).
2) La CPU fixe le sens du transfert « lecture », à l’aide du bus de contrôle.
3) La mémoire programme fournit l’instruction stockée dans la case mémoire, qui est sélectionnée par le bus
d’adresse, sur le bus de donnée.
4) La CPU lit cette instruction.
5) La CPU fournit l’adresse du composant périphérique, à partir de laquelle les données doivent être lues, sur
le bus d’adresse.
6) La CPU fixe le sens du transfert « lecture », à l’aide du bus de contrôle.
7) La périphérie fournit l’information, contenue dans la case mémoire sélectionnée, sur le bus de données.
8) La CPU lit les données.
9) La CPU sélectionne la case de destination « temp » dans la mémoire de donnée, à l’aide du bus d’adresse.
Bus de RD RD WR
contrôle
La tension du bus est stable et valable, elle est soit high ou low
Les contrôleurs 8 bits (8 bit définit ici la largeur du bus des données) possèdent généralement un bus d’adresse
de largeur 16 bits. Ils sont ainsi en mesure d’adresser une zone de mémoire de 64 kilos bytes (0xFFFF). Quant
aux microcontrôleurs 32 bits, ils possèdent un bus d’adresse de largeur 32 bits et sont ainsi en mesure d’adresser
une zone de mémoire de 232 bytes (4 Giga bytes).
La taille de la mémoire dépend du type du contrôleur. Les contrôleur 8 bits possède en général quelques kilo
bytes de mémoire. Cela est suffisant, car ces derniers sont destinés aux applications relativement simples, qui ne
nécessitent pas de systèmes d’exploitation. Les contrôleurs 32 bits, qui sont destinés aux systèmes embarqués,
nécessitent en générale plusieurs Méga bytes de mémoire.
Les décodages d’adresse simples sont réalisés avec de la logique discrète (AND, OR ou 1 of X decoder). Les
composants nécessaires pour réaliser ce genre de décodeur ne sont pas très chers. Toutefois, la logique discrète
nécessite plus de place sur la platine et elle n’est pas flexible. Par conséquent, les erreurs de conception ou les
éventuelles adaptations du plan de mémoire nécessitent souvent une nouvelle platine.
A cause des problèmes de la logique discrète décrits ci-dessus, les décodeurs d’adresse sont souvent réalisés à
l’aide de la logique programmable (GAL, PAL, CPLD etc.). Le décodeur d’adresse correspondant au plan de
mémoire de la Figure 9 peut être réalisé de la manière suivante :
CS0 Flash
CS1 RAM 1
Adresse Logique
program. CS2 RAM 2
CS3 Périphérie
Les équations logiques permettent de programmer ces décodeurs d’adresse. Par exemple les signaux CS pour le
plan de mémoire de la Figure 9 seraient programmés de la manière suivante :
CS1 = /A15
CS2 = A15 * /A14 * /A13
CS3 = A15 * /A14 * A13
CS4 = A15 * A14 * A13
La Figure suivante montre un schéma bloque d’un circuit contenant un décodeur d’adresse ainsi que des
composants de stockages externes et périphériques. Ce circuit contient une Flash de 32 kilos bits, deux RAM de
8 kilos bits et un convertisseur A/D à 1 Byte:
Bus de données
CS4
CS3 32k * 8 8k * 8 8k * 8 Périphérie
Décodeur RAM 1 RAM 2 (convertisseur
CS2 Flash
d’adresse A/D de 1 byte)
CS1
Bus d’adresse
Figure 11: Schéma bloque du circuit avec le décodeur d’adresse, les composants de stockage (Flash et ROM) et
périphérique
Remarque :
Ce schéma bloque ne contient pas les bits de contrôle /RD et /WR.
Le convertisseur A/D est sélectionné avec le chip select CS4. Du fait qu’il ne possède qu’une seule adresse
physique, il sera reproduit dans toute la zone de mémoire réservée à la périphérie. C'est-à-dire que la CPU peut
l’adresser entre 0xE000 et 0xFFFF.
1.5 La mémoire
1.5.1 Les technologies
Fondamentalement il existe les mémoires non volatiles, qui sont utilisée uniquement en lecture, et les mémoires
volatiles, qui peuvent être utilisées en lecture ou en écriture.
• FLASH (la définition exacte est FLASH EEPROM) : Ces mémoires peuvent être effacés électriquement par
bloque durant leur fonctionnement (pas de possibilité d’effacer uniquement des cases, comme avec les
EEPROM). Les FLASH possèdent une grande capacité de stockage (quelques Mégas bytes) et sont, par
conséquent, souvent utilisées pour stocker le code du programme.
Mémoire volatile
Ces mémoires perdent leur contenu dès que leur alimentation est coupée. Ces mémoires sont en générale
qualifiées de RAM (Random Access Memory). Random veut dire que les cases mémoires peuvent être accédées
de façon aléatoire. Il existe les catégories suivantes :
• DRAM (Dynamic RAM) : Ces mémoires stockent l’information à l’aide de condensateur et doivent, par
conséquent, être rafraîchies périodiquement.
• SRAM (Static RAM) : Ces mémoires possèdent une structure plus complexe que les mémoires DRAM mais,
par contre, elles ne doivent pas être rafraîchies périodiquement. Les SRAM sont plus rapide, nécessitent
moins de courant et sont plus cher que les DRAM.
• SDRAM (Synchronous Dynamic RAM) : Ces mémoire sont une version cadencée des DRAM. La fréquence
d’horloge est prédéfinie par le bus du système. Chez les DDR-SDRAM (Double Data Rate SDRAM), les
accès mémoires sont possibles aux flans montant et aux flans descendants de l’horloge.
Aujourd’hui il est également possible de travailler avec des technologies RAM non volatile (FeRAM, MRAM).
Toutefois, ces dernières possèdent une faible capacité de stockage et sont relativement chères.
Mémoire
Adresse Byte
La plus petite adresse 0 0
1 7 0 1
MSB LSB
~ ~
Le nombre de bit d’adresses, qui sont utilisés pour adresser la mémoire, dépend de la taille de la mémoire. Avec
m bits d’adresse, il est possible d’adresser 2m cases mémoires. Par exemple avec 16 bits d’adresses, il est
possible d’adresser un do maire de 64 k (ce qui est souvent utilisé avec de microcontrôleur à 8 bits).
Row-Decoder
A0 .. Memory-Matrix
An
Column-Output
D0 ..
Dn Column-Decoder
An+1
.. Az
OE Control
CS
Les composants ROM possèdent typiquement un bus de données de largeur 8 ou 16 bits. La largeur du bus
d’adresse dépend de la capacité de stockage. Les signaux de contrôle sont composés d’un CS (Chip Select) pour
la sélection du composant, d’un OE (Output Enable) pour activer des amplificateurs de sortie du bus de données.
La Figure 13 ne contient que les signaux de contrôle, qui permettent la lecture de la ROM. La programmation
nécessite des signaux de contrôle supplémentaires.
A0 .. Memory-Matrix
An
Column-In/Output
D0 ..
Dn Column-Decoder
An+1
.. Az
OE Control
CS
R/W
La structure des composants SRAM est identique à celle des composants ROM. Toutefois, le bus de données est
bidirectionnel, afin que la CPU puisse lire et écrire. Un signal de contrôle supplémentaire (Read/Write) est par
conséquent nécessaire, afin de pouvoir définir le sens de la communication. Certain fabriquant utilise même deux
signaux de contrôle distincts (Read et Write) pour réaliser cette tâche.
pouvoir signaliser laquelle de ces adresses est fournie sur le bus d’adresse. Ces signaux sont le RAS (Row
Address Strobe) et le (Column Address Strobe) CAS.
Dout valid
t access RAM
1.5.4 La documentation
Les caractéristiques les plus importantes des composants de stockage sont décrites dans la documentation, qui est
fournie par le fabriquant. Ces dernières sont typiquement:
• La taille de la mémoire (kilo bit ou kilo byte)
• Assignation des pins
• Organisation de la mémoire (bit, byte ou mot)
• Les caractéristiques DC (tension, consommation de courant ...)
• Les caractéristiques AC (temps d’accès ...)
• Table de vérité (logique de contrôle)
Cache
Mémoire de travail
Les registres :
Les registres sont des cases mémoire très rapide, destinées au stockage des opérandes et du résultat de l’ALU. La
CPU peut accéder directement aux registres, c’est à dire sans passer par le système de bus (voir également
chapitre 1.3).
La mémoire cache :
La mémoire cache est une mémoire intermédiaire, qui se situe entre les registres et la mémoire de travail. Cette
mémoire rapide est destinée au stockage des instructions (Instruction-Cache) et des données (Data-Cache) du
programme, qui doivent être utilisées souvent.
Les CPU actuelles sont en mesure de travailler à des fréquences d’horloge supérieures à celles de leurs systèmes
de bus. En d’autres thermes, le temps d’accès à la mémoire de travail est supérieur aux cycles d’horloge interne
du processeur, et représente souvent le talent d’Achille des systèmes à microprocesseur. Par conséquent, lorsque
la CPU lit les instructions ou les données à partir de la mémoire de travail, elle les dépose également dans la
mémoire cache. Si ces données doivent être réutilisées, elles seront chargées automatiquement à partie de la
mémoire cache au lieu de la mémoire de travail. Ce procédé est transparent plus l’utilisateur, c'est-à-dire que le
contrôle la mémoire cache est effectué par le hardware.
On parle d’un « Cache Miss », lorsque des données ne sont pas disponibles dans la mémoire cache. Ces données
seront alors lues à partir de la mémoire de travail et stockées dans un « Cache Line », dont la taille est
typiquement de quelques bytes. Lorsque la mémoire cache est pleine, les « lignes de cache » existantes doivent
être sur écrites.
On parle d’un « Cache Hit », lorsque des données à charger sont disponibles dans la mémoire cache. L’accès à
ces données est plus rapide que si elles devaient être lues à partir de la mémoire de travail.
Les mémoires cache possèdent typiquement une taille de 100 kilos bytes. Toutefois, seul les microprocesseurs
puissants possèdent une telle mémoire.
Les mémoires caches peuvent être échelonnées. Le premier échelon est alors une mémoire cache interne, plus
petite mais très rapide (L1). Le second échelon est une mémoire RAM externe, plus grande mais moins rapide
(L2).
La mémoire de travail :
La mémoire de travail est destinée au stockage des instructions (Flash) et des données (SRAM et SDRAM) du
programme (voir également chapitre 1.5).
Dans le monde des ordinateurs personnels (PC), le code est copié depuis la mémoire de masse dans la RAM, et il
est exécuté à partir de cette dernière.
Dans le monde des systèmes embarqués, le code est exécuté soit à partir de la Flash ou il est copié depuis la
Flash dans la RAM, durant le processus de démarrage (boot), et est exécuté ensuite à partir de cette dernière.
La mémoire de masse :
Les mémoires de masse, comme par exemple les disques dures ou les CD, ne sont pas utilisées dans les systèmes
embarqués. Par contre, ces dernières sont souvent employées dans les PC industriels. Toutefois, il ne faut pas
oublier les désavantages de la mécanique dans les environnements industriels (la température, les vibrations,
l’humidité, la poussière etc.). Par conséquent, on préfère utiliser ici les cartes CF, les cartes SD ou les mémoires
sticks en tant que mémoire de masse.
1.7 La MMU
Die MMU (Memory-Management Unit) est une unité hardware destinée à la gestion de la mémoire. Les
avantages du MMU apparaissent essentiellement avec les systèmes d’exploitation. Avec les processeurs 32 bits,
la MMU est souvent intégrée sur le même chip que celui de la CPU.
Æ Le programme et la CPU travaillent avec des adresses virtuelles. Les accès aux composants de
mémoire s’effectuent avec des adresses physiques.
Processeur
CPU
CPU fournit une
adresse virtuelle Mémoire Mémoire de Peripherie
de travail masse
MMU
MMU fournit une
adresse physique pour
accéder à la mémoire
Bus
La tâche du MMU est de transformer l’adresse virtuelle en une adresse physique ou de réallocation (relocation).
Pour obtenir l’adresse physique, la MMU additionne l’adresse virtuelle à la valeur qui est contenue dans les
registres de réallocation (relocation register). Simultanément il contrôle si l’accès au segment est autorisé ou non,
à l’aide des registres de limitation.
Limit Relocation
register register
jes Work
CPU < + memory
Programm
Virtual Physical
address no address
Lorsque l’on passe d’un programme à un autre, il faut recharger les registres de réallocation et de limite. Ce qui
est la tâche du système d’exploitation. Chez les processeurs, qui ne possèdent pas de MMU, Les adresses
virtuelles correspondent aux adresses physiques.
Des Tableaux de pages sont utilisés pour convertir les adresses virtuelles en adresses physiques. Les adresses
virtuelles sont composées des champs suivants :
1. Le numéro de la page, qui indique l’index du Tableau de pages. Le Tableau de pages contient l’adresse de
base de chaque trame (adresse physique).
2. Un offset de page, qui est additionné au contenu du Tableau de pages (adresse de base) pour construire
l’adresse physique.
Adresse physique
0 1 0 0 0 0 0 0 0 0 0 10 0 0 0
Numéro de trame = 2
obtenu du tableau de
page
15 000 0
14 000 0
13 000 1
12 000 0
11 111 1
Tableau 10 000 0
de page 9 101 1
8 000 0
7 100 1
6 000 0
5 011 1
4 010 1 010
Numéro de page = 4 3 110 1
correspond à 2 000 0
l’indexe du tableau
de page 1 001 1 Offset
bit de
0 000 0 validité
0 1 0 0 0 0 0 0 0 0 0 0 10 0 0 0
Adresse virtuelle
La Figure de l’exemple de ci-dessus montre, que les zones de mémoire virtuelles peuvent être plus grandes que
les zones de mémoire physiques. Le bit de validité indique si le contenu du Tableau de pages est valide ou pas.
Si ce n’est pas le cas, les données doivent d’abord être copiées depuis la mémoire de masse dans la mémoire de
travail.
Le calcul des adresses physique peut être optimisé à l’aide des tampons auxiliaires de traduction (Translation
Look aside Buffers abrégé en TLB). Ces derniers sont également intégrés dans la MMU. Le TLB est un Tableau
contenant 8 à 64 entrées, qui correspondent aux entrées des Tableaux de pages.
Le TLB contient des informations sur les pages : numéro de la page, trame, bit de validité, bit de modification
(montre si le contenu d’une trame a été modifié et, par conséquent, si ce contenu doit être sauvé avant son
élimination) et bits de protection (droit d’accès sur une trame donnée).
Le processus de la conversion de l’adresse virtuelle en une adresse physique est le suivant : D’abord la MMU
contrôle si la page recherché existe dans le TLB. Pour cela la demande est comparée avec les numéros de page
du TLB. Lorsqu’il y a correspondance (TLB hit), le numéro de la trame est lu directement à partir du TLB.
Lorsqu’il n’y a pas de correspondance (TLB miss), la MMU continue la recherche avec le Tableau de pages.
Finalement une demande est choisie dans le TLB et est remplacée avec le numéro de la page, contenu dans le
Tableau de pages. Le TLB contient par conséquent tous les numéros de pages, qui ont été utilisés récemment.
Adresse virtuelle
Page Frame
Number Number
TLB hit
TLB
Page Table
1.8 DMA
L’accès direct à la mémoire (Direct Memory Access abrégé par DMA) est un procédé, qui permet de transférer
rapidement les données entre la mémoire de masse, ou les composants périphériques, et la mémoire de travail.
Un contrôleur DMA est nécessaire pour cela.
Sans le contrôleur DMA, les transferts des données entre la périphérie et la RAM doivent passer par la CPU.
Dans ce cas, ce dernier doit gérer les adresses source, les adresses de destination et les registres tampons. Ce
transfert doit bien entendu être programmé et nécessite, par exemple, plusieurs cycles de bus pour ne transférer
CPU
Le contrôleur DMA permet de transférer les données sans que ces derniers passent par la CPU. La CPU est alors
découplé du bus, afin que contrôleur DMA puisse reprendre le contrôle de ce dernier. La tâche de la CPU se
limite dans ce cas à l’initialisation du contrôleur DMA (adresse source, adresse destination et quantité de
données).
CPU Contrôleur
DMA
1.9 La périphérie
Les composants suivant sont considérés comme périphériques :
a) Les composants qui permettent d’accéder aux données de l’environnement du système. Ces données sont lues
avec des senseurs ou d’autre type d’interfaces.
b) Les composants qui permettent de transmettre les données à l’environnement du système. Ces données sont
transmises avec des actuateurs ou d’autres types d’interface.
La périphérie représente l’interface entre le processeur et son environnement. Les composants périphériques sont
en général connectés au bus du processeur.
D
Port Port Port
Out Out Out
Open Drain
Les sorties Open Drain (avec la technologie FET) ou Open Collector (avec la technologie bipolaire) possèdent
un seul transistor, qui permet de tirer une résistance (Pull-up) contre la masse. Les résistances pull-up peuvent
être sois internes ou externes. Les sorties Open Drain et Open Collector peuvent être connectées en parallèle,
comme par exemple avec les sources d’interruption :
IRQx
Tri state
Les sorties tri state peuvent avoir un état à haute impédance. L’état de sortie active ou « tri state » est activée à
l’aide d’un signal de contrôle supplémentaire (G).
En générale les sorties, qui fournissent des signaux sur un bus, sont réalisées avec des tri states, comme par
exemple les entrées et sorties des données (I/O) d’une RAM.
1) Utilisation de pins du microcontrôleur, appelés GPIO. Le nombre de ces pins varie en fonction de la famille
des microcontrôleurs. L’accès à ces derniers s’effectue à l’aide de registres.
2) Utilisation de composants périphériques supplémentaires (FPGA, PIO, …), qui mettent à disposions des I/O.
La communication avec ces composants s’effectue avec le système de bus.
Les entrées et sorties digitales sont utilisées pour connecter les éléments d’affichage (lampes, LED etc.), les
relais, les moteurs, les soupapes etc.
Les sorties digitales possèdent souvent un étage d’amplification, afin qu’elles puissent fournir le courant
nécessaire à la transmission (ex. contrôle des moteurs, des soupapes etc.). Les entrées digitales possèdent
souvent des filtrés (circuit RC et trigger de Schmitt). Elles sont également protégées contre les sur tensions
Contrôleur de
Microcontrôleur moteur
Motor
GPIO1
GPIO2
Interrupteur
…
Protection
d’entrée
Les propriétés les plus important de la transmission sérielle sont les suivants :
• Le nombre de bit par seconde (baud rate)
• La tension
• Le protocole
CPU
1.9.3.1 RS232
L’interface RS232 est très importante dans le domaine des systèmes embarqués. Cette interface permet par
exemple de gérer un affichage LCD ou une ligne de commande (Command Line Interface). La plupart des
microcontrôleurs possèdent déjà une interface RS232. Toutefois, les tensions de sortie de ces dernières
correspondent au niveau TTL. Par conséquent, un convertisseur de tension externe doit être ajouté au système.
transmission (baud rate), le format des données, le bit de parité etc.) et les interruptions.
Les interfaces RS232 soutiennent différents modes de fonctionnement. Par exemple ils peuvent fonctionner soit
en mode synchrone ou en mode asynchrone. La fréquence de transmission est souvent définie avec un timer
standard. Certaine interface RS232 possèdent toutefois leur propre générateur de fréquence. Le timer peut ainsi
être utilisé pour d’autres applications.
Transmitter
Transmit FIFO
Control Baudrate
Unit Generator
Receiver
Receive Shifter RxD
Receive FIFO
1.9.3.2 SPI
L’interface SPI (Serial Peripheral Interface) est un système de bus sériel à haut débit, destiné à la communication
entre le microcontrôleur et la périphérie. Ce dernier est souvent utilisé pour la communication avec des
extensions I/O, des affichages LCD ainsi que des convertisseurs A/D et D/A. Il peut également être utilisé pour
la communication entre microcontrôleurs.
7 0 7 0
Participant 1 Participant n
L’interface SPI est toujours utilisée en mode maître esclave. Le maître est alors responsable de la génération de
la fréquence d’horloge. Le SPI peut travailler de façon duplexe à l’aide de deux lignes de transmission : MOSI
(Master Out Slave In) et MISO (Master In Slave Out). Les esclaves peuvent être connectés soit de façon
parallèle (c’est à dire que toutes les sorties des esclaves sont rassemblée et connectées à l’entré MISO du maître)
ou de façon sérielle (la sorite d’un esclave est connectée à l’entrée du prochain esclave et la sortie du dernier
esclave est connecté à l’entrée MISO du maître).
Le microcontrôleur écrit les données à transmettre dans un tampon de transmission. Ces dernières sont
sérialisées à l’aide d’un registre à décalage (comme une transmission RS232). Les données reçues sont
également converties à l’aide d’un registre à décalage. Le microcontrôleur peut alors lire ces données de façon
parallèle dans le tampon de réception. Du fait que les interfaces SPI ont une bande passante relativement élevé,
les tampons de transmission et de réception contiennent souvent plusieurs bytes.
Donnée Donnée
Registre à Registre à
décalage décalage
MOSI
MISO
SCK
FIFO de FIFO de
SPI maître réception tansmission
1.9.3.3 I2C
Le bus I2C (Inter Integrated Circuit) a été développé par l’entreprise Philips. Ce bus est utilisé pour connecter
les composants périphériques comme l’ EEPROM, les affichages LCD ou RTC (Real Time Clock) au
microcontrôleur.
Le bus I2C est composé de deux fils, ce qui réduit la partie hardware de façon drastique. Ce bus comprend une
ligne d’horloge SCL (Serial Clock) et une ligne de donnée SDA (Serial Data). La communication est par
conséquent synchrone. Les modes d’utilisation du bus sont maître et esclave.
Figure 32 : Le maître (transmetteur ) adresse l’esclave (récepteur) avec une adresse composé de 7 bits et envoi 2
bytes de données
Le nombre de bytes à transmettre et le sens de la transmission peuvent varier selon les composants utilisés. Ces
informations sont fournies par documentation de ces derniers. Par exemple : le maître n’écrit qu’un byte dans
un convertisseur D/A. Alors qu’avec une EEPROM, qui possède une zone de mémoire interne, le maître doit
d’abord envoyer l’offset de la case mémoire à accéder. Le transfert de données n’est effectué qu’ensuite,
jusqu'au bit d’arrêt.
Le Tableau suivant illustre quelques exemples de transmission de données avec l’interface I2C (le bit
confirmation n’est pas affiché) :
Composant Protocole
Convertisseur Démarrage Adresse Wr Données Arrêt
D/A de
l’esclave
Ecriture RAM Démarrage Adresse Wr Données Données Arrêt
de
l’esclave
Lecture RAM Démarrage Adresse Wr Données Arrêt Démarrage Adresse Rd Données Arrêt
de de
l’esclave l’esclave
Figure 33 : Exemple pour le protocole I2C
SDA
SCL
Start Stop
SDA 1 0 0 1 0 1 1 1
SCL
Dans la Figure 35, le maître envoi en premier la condition de démarrage, ensuite il envoi l’adresse de l’esclave
(1001011) et pour finir le bit R/W. L’esclave doit signaler sa présence avec le bit de confirmation. Il est
important que le maître maintienne le SDA à un état haute impédance durant cette phase. Les données peuvent
être transmis après le bit de confirmation (cela n’est pas montré dans la Figure). La communication est
interrompue avec le bit d’arrêt.
Admettons que l’on veut connecter un émetteur à plusieurs participants avec un bus I2C. Dans ce cas tous les
participants doivent se mettent dans un état à haute impédance. Certain microcontrôleur peuvent conFigurer leurs
sorties en tri state. Si cela n’est pas possible, il faut conFigurer les pins SCL et SDA en entrée.
1.9.4 Le timer
Le timer est utilisé comme base de temps dans les processeurs (système d’exploitation, horloge etc.) ou pour les
composants externes (ex. signal PWM, horloge pour les interfaces sérielles ...).
Oscillateur
Timer
Mode Timer / Counter Overflow
Register Flag
Counter
Entrée
externe
En générale des registres de 8 ou 16 bits sont utilisés dans les timers ou les compteurs. Dans le mode timer, ces
registres sont incrémentés périodiquement à l’aide de l’horloge interne du système. Dans le mode compteur,
cette incrémentation s’effectue en fonction de signaux externes.
Le mode « de rechargement automatique» (auto reload) permet d’initialiser le registre du timer avec une valeur
prédéfinie, qui est contenue dans le « registre de rechargement » (reload register). Ce qui permet de générer des
signaux avec des périodes variables.
Reload
Reload
Register
Dans le mode de rechargement automatique, il n’y a pas seulement une mis à un du flag overflow, mais le
compteur du timer est également initialisé avec la valeur du registre de rechargement.
Les caractéristiques les plus important pour un convertisseur A/D sont les suivants :
• Largeur de bit (les valeurs typiques sont 8, 10, 12 ou 16 bits)
• Temps de conversion
Control-Register Data-Register
CPU
Les caractéristiques les plus important pour un convertisseur D/A sont les suivants:
• Largeur de bit (les valeurs typiques sont 8, 10, 12 ou 16 bits)
• Temps de conversion
La conversion D/A peut être également effectuée avec une sortie PWM, qui est connectée à un filtre passe bas.
Complex Instruction Set Computer (CISC): Les instructions peuvent être relativement complexes. Une seule
instruction assembleur est subdivisée en une séquence de micro instructions. L’exécution d’une instruction
nécessite donc plusieurs cycles d’horloge. Le micro programme est intégré par le fabricant. Par conséquent,
l’utilisateur n’y a pas d’accès.
Reduced Instruction Set Computer (RISC): Les instructions sont relativement simples. Il n’y a pas se micro
instructions et les instructions sont exécutées directement.
• PWM
• CAN
• USB
PIC Le PIC est une famille de contrôleur de 8, 16 et 32 bits de Microchip. Il est utilisé avant tout
dans les applications à bas prix et à basse consommation.
68HCxx Les familles 8 bits de Motorola 68HC05, 68HC08 et 68HC11 sont très répandues dans
l’industrie et dans l’enseignement. Cependant, elles ne sont plus très utilisées dans les
nouvelles applications.
MC683xx Le contrôleur 32 bits de Motorola se base sur la CPU32. Les registres de la CPU ont une
largeur de 32 bits. En fonction du contrôleur, le bus de données peut contenir 16 à 32 bits et
le bus d’adresse 24 à 32 bits. Ces contrôleurs sont utilisés pour des applications, qui
nécessitent des puissances de calcul moyennes, comme la télécommunication et l’industrie
automobile. La famille est construite de façon modulaire et contient plusieurs membres.
Cette famille est remplacée dans les nouvelles applications par celle du ColdFire.
ColdFire La famille des ColdFires remplace celle des MC683xx de Motorola. ColdFire est un
processeur de 32 bits pur, c. à d. qu’il contient des bus d’adresse et de données de 32 bits.
Les processeurs ColdFire possèdent la même architecture que ceux 683xx. Toutefois, ils
sont plus puissants que leurs prédécesseurs.
ARM La société ARM propose des cœurs divers, qui sont fabriqués sous licence par différentes
compagnies. ARM est une abréviation pour Advanced RISC Machine. Actuellement les
familles ARM7 et ARM9 (tous deux à 32 bits) sont très répandues dans le domaine des
contrôleurs embarqués. Ces contrôleurs fournissent une bonne puissance de calcul pour une
consommation relativement basse.
MSP430 Le MSP40 est une famille de contrôleur du type RISC à 16 bits, produite par TI. Il est
utilisé avant tout dans les applications à consommation ultra basse (ex. les appareils à
batteries).
AVR, SAM Différentes familles de contrôleur de la compagnie Atmel comme AVR (processeur 8 bits
RISC), AVR32 (architecture 32 bits avec un DSP et la fonctionnalité SIMD) ou AT91SAM
(contrôleur très bon marché qui se base sur l’architecture ARM7). Ces contrôleurs ont une
grande croissance dans le domaine de l’électronique embarqué.
2 Le microcontrôleur PXA270
La première partie de ce chapitre traite les différentes versions de l’architecture ARM. La seconde partie décrit le
digramme de block et le modèle des registres du microcontrôleur PXA270 de Intel. Ce chapitre se limite
toutefois aux tâches, qui sont prioritaires pour la programmation de ce dernier. Les tâches plus spécifiques sont
fournies dans les manuels d’utilisateur Intel [1] à [8] ou par la littérature spécialisée [10] ou [11].
Les microprocesseurs, qui sont basés sur l’architecture ARM, sont actuellement très répondus. Les raisons en
sont multiples :
• Architecture commune
• Puissance de calcul élevée
• Faible consommation (typiquement moins d’un Watt)
• Coût réduit
Les caractéristiques les plus importantes de l’architecture ARM sont les suivantes :
• Les caractéristiques typiques de l’architecture RISC :
− Un grand banc de registre à 32 bits
− Architecture « Load / Store », car les opérandes doivent être copiées dans les registres. La CPU ne peut
pas traiter les données, qui sont stockées dans de la mémoire.
− Les instructions sont orthogonales
− Adressage simple à l’aide des registres standard
− Trois modes d’adressage
− La longueur des instructions est fixe (32 bits). Ce qui simplifie le décodage des instructions.
• Les propriétés typiques de l’architecture ARM:
− Une instruction unique permet d’utiliser l’ALU avec le « barrel shifters » (shift left / right)
− Incrémentations et décrémentations automatiques des différents modes d’adressages
− Opérations « Load / Store » simultanées pour plusieurs registres
− Exécution conditionnelle des instructions (conditional execution)
− Les types de données byte (8 bits), halfword (16 bits) et word (32 bits)
− Les jeux d’instructions ARM à 32 bits, Thumb à 16 bits et Jazelle. Ce dernier est prévu pour l’exécution
du code byte Java
• L’ARM possède un système de traitement des interruptions simple et efficace, avec des bancs de registres
fantômes. Ces derniers sont mis à disposition pour les différents types d’exception, qui sont les suivants :
− Les interruptions rapides
− Les interruptions normales
− Memory abort (pour la MMU et la protection de la mémoire)
− Exécution d’une instruction non définie
− Les interruptions software
• Les registres d’état : L’état actuel du processeur est stocké dans le registre « Current Program Status Register
(CPSR) » (voir chapitre 2.6):
− 4 bits d’état
− 2 bits pour la désactiver les interruptions
− 5 Bits pour le mode du processeur
− 1 bit pour le mode ARM ou Thumb
ARM propose plusieurs versions d’architecture. Les nouvelles versions sont compatibles avec les anciennes. Ces
dernières mettent à disposition de nouvelles instructions et de nouvelles fonctionnalités hardware. Chaque
version d’architecture est déclinée en différentes familles, qui à leur tour possèdent différents cœurs (Core). Le
Tableau suivant en fournit un aperçu :
Divers familles de processeur ARM sont proposées pour une version d’architecture donnée (Par exemple :
ARM7, ARM9, ARM10 etc.). Au sein d’un famille il existe plusieurs cœurs (Par exemple : ARM7TDMI,
ARM{x}{y}{z}{T}{D}{M}{I}{E}{J}{F}{S}
x: Famille
y: Gestion de mémoire / unité de protection
z: Mémoire cache
T: Décodeur d’instruction Thumb à 16-bit
D: Débuggeur JTAG
M: Multiplication rapide
I: Embedded ICE Macrocell
E: Instruction améliorée
J: Jazelle (code byte Java)
F: Unité à virgule flottante vectorielle
S: Version synthétisable
Tous les cœurs, qui ont été développés après le ARM7TDMI, contiennent les fonctionnalités TDMI.
Aujourd’hui il existe beaucoup de fournisseur de technologie silicium, qui possèdent des licences du cœur ARM
et qui les implémentent dans leur microcontrôleur. Voici un aperçu de ces fabricants : Analog Device, Atmel,
Freescale, Infineon, Intel, NEC, Philips, Samsung, TI et Toshiba.
• Une mémoire cache pour les instructions de 32 kilos bytes (Instruction Cache abrégé par I-Cache). Les
segments de codes, qui sont souvent exécutés, sont stockés momentanément dans l’I-Cache. Le cœur du
processeur peut accéder à ces instructions à la fréquence d’horloge interne. Le I-Cache travaille avec des
adresses virtuelles ! Il améliore la performance du processeur, mais il n’influence pas son comportement de
façon déterminante.
• Une mémoire cache pour les données de 32 kilos bytes (Data Cache abrégé par D-Cache), qui permet de
stocker momentanément les données. Le D-Cache contient les données, qui sont souvent utilisées comme Par
exemple : les Tableaux, les coefficients etc.
• Mini D-Cache : Mémoire intermédiaire supplémentaire de 2 kilos bytes pour des données, qui doivent être
modifiées rapidement comme les entrées vidéos MPEG. Ce qui empêche que les données, qui doivent être
souvent modifiées, ne remplissent la mémoire cache.
• Write Buffer: Mémoire intermédiaire de 8 fois 16 bytes pour les données, qui doivent être stockées dans la
mémoire externe.
• La MMU pour les instructions (abrégé en anglais par IMMU) : Le IMMU est responsable de la conversion
des adresses virtuelles des instructions en adresses physiques, des droits d’accès, de la gestion des domaines
de la mémoire et du contrôle des attributs de l’I-Cache. Le IMMU contient un TLB avec 32 entrées selon le
principe de Round-Robin.
• La MMU pour les données (abrégé en anglais par DMMU): le DMMU est responsable de la conversion des
adresses virtuelles des données en adresses physiques, des droits d’accès, de la gestion du domaine de la
mémoire et du contrôle des attributs du D-Cache ou du mini D-Cache. Le DMMU contient également un
TLB avec 32 entrées.
• Branch Target Buffer (BTB): Le BTB réalise des prévisions statistiques sur le déroulement des sauts de
programme conditionnels. Cela est important pour le pipeline des adresses des instructions (voire chapitre
2.2.3). Lorsque cette prévision est erronée, il faut vider le pipeline (pipeline flush) afin de le remplir avec des
nouvelles instructions. Les prévisions suivantes sont possibles :
− Strongly taken: Le saut de programme a toujours été réalisés jusqu’à présent. L’adresse de destination
sera stockée.
− Weakly taken: Le saut de programme a souvent été réalisé jusqu’à présent. L’adresse de destination sera
stockée.
− Weakly not taken: Le saut de programme n’a été réalisés que rarement. L’adresse de la prochaine
instruction sera stockée.
− Strongly not taken: Le saut de programme n’a jamais été réalisé. L’adresse de la prochaine instruction
sera stockée.
• Core Memory Bus: Ce bus interne de 64 bits possède une bande passante de 4.8 Gigas Bytes / sec. Ce bus est
capable de lire 32 bits et d’écrire 32 bits simultanément en une seule période d’horloge. La bande passante de
correspond donc à 2.4 Gigas Bytes / sec pour chaque sens.
• Coprocessor Interface: Transfert de données de 32 bits entre le coprocesseur et la mémoire cache des
données.
• Coprocessor 15 (CP15): Les registres de contrôle des mémoires cache, de la MMU et du TLB.
• Coprocessor 14 (CP14): Les registres de contrôle du moniteur de performance (Performance Monitoring), du
débuggeur software et du mode d’horloge / puissance. Le moniteur de performance permet d’analyser le
comportement de la mémoire cache, celui du TLB ou la précision des prévisions du Branch Target Buffer.
• Multiply-Accumulate Coprocessor (CP0): Ce coprocesseur permet de réaliser des multiplications 16*16 bits,
en mode standard ou en mode dual (SIMD), et des multiplications 16*32 bits avec un accumulation de 40
bits.
• JTAG : « Boundary Scan Interface » est un interface standard destine aux testes.
• Debug : Le Debug permet de déboguer le programme durant son exécution (break points pour les instructions
et les données, Trace Buffer), en utilisant l’interface JTAG.
• Trace Buffer: Pour le déroulement de l’exécution du programme.
Data
Sign extend
Write Read
Barrel shifter
A B Acc
ALU MAC
Result-Bus
Address register
Incrementer
Address
Le traitement des données au sein des ARM / XScale s’effectue toujours à l’aide de registres. Il n’existe pas
d’instruction, qui soit capable de traiter directement de contenu de la mémoire. Les données sont donc
transférées dans le banc de registres, qui est composé de 16 registres de 32 bits.
La plupart des instructions ARM interprètent le contenu des registres comme des valeurs entières 32 bits
(signées ou non signées). L’unité « Sign extend » permet de convertir les valeurs signées 8 et 16 bits, qui ont été
lues à partir de la mémoire externe, en des valeurs signées 32 bits.
La plupart des instructions ARM traite deux opérandes (Rn et Rm). Le contenu de ces registres est transférer
avec les bus internes A et B. Le second opérande Rm peut être également décalé, soit vers la gauche ou vers la
droite, avec le « Barrel Shifter ».
L’ALU (Arithmetic Logic Unit) ou le MAC (Multiply Accumulate Unit) traitent les opérandes Rn et Rm, et
écrivent le résultat dans le registre (Rd).
Les instructions Load / Store permettent de transférer les données, soit à partir de la mémoire externe dans un
registre, ou inversement à partir d’un registre dans la mémoire externe. L’adresse de la case mémoire à
sélectionner est calculée par l’ALU. Cette dernière est copiée dans le registre d’adresse, afin d’être fournie sur le
bus d’adresse. L’incrémenter (Incremented) permet d’incrémenter ou de décrémenter cette adresse, après chaque
opération d’écriture et de lecture.
Les trois étages (stage) du pipeline doivent être exécutés pour chaque instruction. L’exécution de chaque étage
nécessite un cycle d’horloge. Par conséquent, l’exécution d’une instruction complète nécessite trois cycles
d’horloge. Ce système permet toutefois d’exécuter une instruction par cycle d’horloge, car le pipeline traite
simultanément plusieurs instructions. L’exemple suivant illustre ce procédé :
Cycle 1 Instr. 1
Tous les 3 pipelines sont en mesure d’exécuter leurs instructions en parallèle. Les tâches des différents étages
sont les suivantes :
• F1 / F2, Instruction Fetch: Livre au niveau ID la prochaine instruction à exécuter. Cette dernière est lue à
partir du cache d’instruction (I-Cache) en fonction du Programme Counter (PC). La différence de la valeur
du PC entre les niveaux F1 et X1, correspondant à 16 bytes, est définie intrinsèquement par le pipeline du
XScale. En présence de sauts de programme conditionnel, l’adresse de la prochaine instruction est définie par
Figure 43 : Diagramme de bloc des PXA27x. Source : XScale PXA27x Family Developers Manual [2]
Le XScale PXA270 possède, en plus du cœur de XScale décrit au chapitre 2.2, les composants périphériques
suivants :
• Coeur XScale, dont les propriétés sont décrites au chapitre 2.2.
• Gestionnaire de consommation (Power-Management) flexible pour les applications à basse consommation
• SRAM interne de 4 * 64 kilos bytes
• Un contrôleur d’interruption (Interrupt-Controller) pour le masquage ou l’activation des interruptions. Ce
contrôleur est accessible à l’aide du plan de mémoire ou des registres du coprocesseur.
• Timer pour les systèmes d’opérations, un watchdog et 8 canaux timer supplémentaires.
• Unité PWM avec 4 canaux indépendants
• Real Time Clock (RTC) avec un conteur 32 bits pour le Timer, les interruptions périodiques etc.
• General Purpose I/O (GPIO): Beaucoup de pins du PXA27x sont multifonctionnels et peuvent être utilisés
comme entrées et sorties digitales.
• Memory Controller avec de nombreuses interfaces pour les composants de stockage internes et externes
(SDRAM, Flash, Card). Cette unité pilote le bus d’adressage, de données et de contrôle.
• Direct Memory Access Unit (DMA) pour les transferts de données de mémoire à mémoire, de périphérie à
mémoire et de mémoire à périphérie.
• De nombreuses interfaces sérielles
− 3 UART (Standard, Bluetooth, Full-Function)
− Port de communication infrarouge rapide
− Bus I2C
− AC '97 Codec Interface
− I2S Audio Codec Interface
− Contrôleur USB client et hôte
Les modes FIQ, IRQ, SVC, ABT et UND sont des modes destinés aux traitements des interruptions (voir
chapitre 7 : Les interruptions). Le processeur exécute toutes les applications standard dans le mode utilisateur.
Lorsque des deux requêtes d’interruption hardware (FIQ ou IRQ) apparaissent, le processeur entre dans le mode
d’interruption correspondant à la requête afin de la traiter. Le processeur se trouve dans le mode superviseur
après le reset. Les routines du système d’exploitation sont exécutées dans ce mode. Le processeur entre dans le
mode abandon (ABT), lorsque apparaissent des erreurs d’accès mémoire. Le mode non défini est choisi lorsque
le processeur doit exécuter des instructions, qui ne sont pas définies ou qui ne sont pas soutenues. Le mode
système est identique à celui utilisateur. Ce dernier permet toutefois d’accéder aux registres CPSR (voir chapitre
2.6).
R0 – r15 16 registres d’utilités générales de 32 bits (general purpose), destinées au stockage des données
et des adresses.
Les trois derniers registres de ce banc sont utilisés normalement pour des tâches spéciales :
r13 en tant que « stack pointer » (sp), contient l’adresse actuelle de la pile du mode momentané
r14 en tant que « link register » (lr), contient l’adresse de retour dans le cadre des appelles de
sous routine.
r15 en tant que « program counter » (pc), contient l’adresse de la prochaine instruction, qui
doit être lue (fetch)
CPSR Current Program Status Register, voir chapitre 2.6
SPSR Saved Program Status Register
L’accès aux 16 registres d’utilité général est possible dans tous les modes de fonctionnement. Mis à part
quelques exceptions, ces registres peuvent être utilisés librement.
Fast
User und interrupt Interrupt
System Request Supervisor Abort Undefined
Request
r0 r0 r0 r0 r0 r0
r1 r1 r1 r1 r1 r1
r2 r2 r2 r2 r2 r2
r3 r3 r3 r3 r3 r3
r4 r4 r4 r4 r4 r4
r5 r5 r5 r5 r5 r5
r6 r6 r6 r6 r6 r6
r7 r7 r7 r7 r7 r7
r8 r8_fiq r8 r8 r8 r8
r9 r9_fiq r9 r9 r9 r9
r10 r10_fiq r10 r10 r10 r10
r11 r11_fiq r11 r11 r11 r11
r12 r12_fiq r12 r12 r12 r12
r13 sp r13_fiq r13_irq r13_svc r13_abt r13_und
r14 lr r14_fiq r14_irq r14_svc r14_abt r14_und
r15 pc r15 pc r15 pc r15 pc r15 pc r15 pc
Les registres r0 à r8 sont communs pour tous les modes de fonctionnement. Ce ne sont donc pas des registres
fantômes.
Les registres r8 à r14 sont par contre des registres fantômes (banked register). Chaque mode de
fonctionnement possède son propre banc de ces registres (voir Figure 45). Les registres r8 à r12 ne sont pas
réservés pour des tâches spécifiques.
Le registre r13 est utilisé comme pointeur de pile (stack pointer abrégé par sp).
Le registre r14 est également appelé registre de reliage (link register abrégé par lr). L’adresse de retour est
copiée dans ce registre, lorsque le programme fait un saut dans une sous routine. Cette opération est également
exécutée avec les routines de service d’interruption. Toutefois, le registre du nouveau mode est utilisé dans ce
dernier cas.
Le registre r15 sert de compteur de programme (program counter abrégé par pc).
L’APCS (ARM Procedure Call Standard) spécifiée plus précisément l’utilisation des registres (voire chapitre
6 « Les sous routines »).
La Figure 45 montre que le mode d’interruption standard partage les registres r0 à r12 avec mode utilisateur.
Dans ce cas, seules les registres r13 et r14 sont des registres fantômes (banked register). C'est-à-dire que
uniquement ces deux registres diffèrent en fonction du mode de fonctionnement. L’avantage de cette technique
est que ces registres peuvent être modifiés dans un mode donné, sans que cela n’influence les autres modes. Ce
sujet est traités plus en détail dans les chapitre 6 « Les sous routines » et le chapitre 7 « Les interruptions ». Le
Saved Program Status Register (SPSR) contient la valeur du Saved Program Status Register (CPSR) du mode
précédent.
Bit 31 30 29 28 7 6 5 4 0
Les sauts de programme conditionnels sont toujours réalisés en fonction des bits d’état. Si le programme contient
par exemple l’instruction « if (A == 0) », il exécutera un saut sera en fonction du bit Z. Les processeurs
ARM possèdent en supplément des instructions conditionnelles, qui sont également exécutées en fonction des
bits d’état (Conditional Execution).
Le Tableau suivant contient les bits d’états du registre CPSR ainsi que leur description :
SUB ADD
231
Nulle
0
-1 1
2
N=1 N=0
SUB Négatif Positif ADD
- 231 - 1 231
V=1 V=1
Dépassement avec Dépassement avec
addition soustraction
Les processeurs ARM - à partir de la version v4 - soutiennent les formats de données suivants :
• Byte signés et non signés (8 bits, char)
• Demi mot (halfword) signés et non signés (16 bits, short int)
• Mot (word) signés et non signés (32 bits, long int)
Les processeurs ARM sont basés sur l’architecture Load/Store. C'est-à-dire que les opérandes de l’ALU doivent
toujours être stockées dans des registres. Les opérateurs de l’ALU traitent ainsi les données sous formant mot
(32 bits). Les seules instructions, qui sont capables de transférer des données entre la mémoire et les registres,
sont les instructions de transfert de donnée (Load et Store). Ces instructions sont capables de transférer des
données du type byte, demi-mot et mot. Lorsqu’une donnée du type byte ou demi-mot est lue à partir de la
mémoire, elle est étendue à 32 bits avec l’unité d’extension de signe « Sign Extend » (voir Figure 40).
Certaine conditions doivent être respectées pour le stockage des données dans la mémoire. Les Figures suivantes
illustres le stockage des bytes, des demi mots et des mots dans la mémoire :
Adresse
N Byte 0
n+1 Byte 1
n+2 Byte 2
n+3 Byte 3
Figure 49: Model de mémoire du type byte
Adresse
n Demi mot 0 Byte de poids plus faible du demi mot 0
Byte de poids plus fort du demi mot 0
n+2 Demi mot 1 Byte de poids plus faible du demi mot 1
Byte de poids plus fort du demi mot 1
Figure 50: Model de mémoire du type demi mot
Un demi mot est toujours stocké a une adresse paire (frontière 2 bytes). Le byte de poids plus faibles sera stocké
à l’adresse inférieure (paire), et le byte de poids plus for à l’adresse supérieur (impaire). Le bit de poids le plus
faible de l’adresse de base A0 est donc nul.
Adresse
n Mot 0 Byte de poids le plus faible du mot 0
Un mot est toujours stocké à une adresse, qui est divisible par 4. Le byte de poids le plus faible du mot est stocké
à l’adresse n, alors que le byte de poids le plus fort à l’adresse n+3. Les deux bits de poids les plus faibles de
l’adresse de base (A0 et A1) sont donc nuls.
0xFFFF_0800 Réservé
0xFFFF_0000 High Vector Debug Handler
0xA400_0000 Réservé
0xA000_0000 64 M bytes SDRAM
0x5800_0000 Réservé
0x4000_0000 Périphérie
0x3000_0000 Compact Flash 1
0x2000_0000 Compact Flash 0
0x1800_0000 Réservé
0x1400_0000 nCS5
0x1000_0000 nCS4
0x0C00_0000 CPLD, MIN3IO (nCS3), Extension 1 bis 4
0x0800_0000 Ethernet-Controller (nCS2)
0x0400_0000 CAN-Controller (nCS1)
0x0200_0000 Réservé
0x0000_0000 32 M byte Flash (Vector Table)
Figure 53 : Le plan de mémoire du kit de développement CARME
pouvoir gérer la communication. Les Figures suivantes illustrent des connexions typiques des composants Flash
et SDRAM, qui possèdent une organisation de 16 bits.
nCS nCS
nWE nWE
nOE nOE
nADV nADV
CLK CLK
MA<23:2> MA<23:2>
A<21:0> A<21:0>
MD<15:0> MD<31:16>
DQ<15:0> DQ<15:0>
Figure 55: Schéma bloque de la connexion des composants flash avec une organisation de 16 bits
Source: Intel PXA27x Processor Family Developer's Manual [2]
Signal Description
CS Chip-Select : Sélectionne un banc de composants de stockage. Un banc représente un certain
nombre de composants, qui mis ensemble sont en mesure de traiter des données de 32 bits.
Le banc dans l’exemple de ci-dessus est composé de 2 composants avec une organisation de
16 bits.
WE Write Enable : Signal d’écriture, avec lequel les données peuvent être écrites dans la flash.
OE Output Enable : Signal de lecture avec lequel les données peuvent être lues à partir du flash.
Ce signal active les amplificateurs de sortie pour le bus de données.
ADV Address Valid : Ce signal indique que les adresses, qui sont fournies par le processeur sur le
bus d’adresse, sont valables. Correspond au signal de sortie du processeur nSDCAS.
CLK Horloge pour tous les composants statiques comme les flashs ou les SDRAM
A Bus d’adresse : Seul les données du type mots sont lues dans cet exemple (32 bits). Par
conséquent les bits d’adresse A0 et A1 n’y sont pas utilisés.
DQ Bus de données : Les composants de stockage possèdent une organisation de 16 bits. Par
conséquent, le composant de gauche est connecté aux 16 bits de données de poids plus
faibles (low halfword) et celui de droite aux 16 bits de données de poids plus fort (high
halfword).
Tableau 6 : Signaux de contrôle destinés au composant flash
nCS
nWE
nSDRAS, nSDCAS
nSDCKE, nSDCLK<2:1>
MA<24:10>
DQM<3:0>
MD<31:0>
nCS nCS
nWE nWE
nRAS nRAS
nCAS nCAS
CKE CKE
CLK CLK
MA<21:10> MA<21:10>
A<11:0> A<11:0>
MA<23:22> MA<23:22>
BA<1:0> BA<1:0>
DQM0 DQM2
DQML DQML
DQM1 DQM3
DQMH DQMH
MD<15:0> MD<31:16>
DQ<15:0> DQ<15:0>
Figure 56 : Schéma bloque de la connexion des composants SDRAM avec une organisation de 16 bits
Source : Intel PXA27x Processor Family Developer's Manual [2]
Signal Description
CS Chip-Select : Sélectionne un banc de composants SDRAM
WE Write Enable : Signal d’écriture, avec lequel les données peuvent être écrites dans la
SDRAM.
RAS Row Address Strobe
CAS Column Address Strobe
CKE Clock Enable
CLK Clock
A Adresses pour row et column
BA De nombreux SDRAM proposent des bancs de mémoire internes. Ces derniers peuvent être
sélectionnés à l’aide des signaux « Bank Address ». Ces signaux sont connectés côté
processeur aux bits d’adresses.
DQML/DQMH Data byte mask control. Ces quatre lignes de contrôle du processeur permettent de transférer
les données de façon byte ou demi mot : DQM<0> correspond au byte de poids le plus faible
(bit 0 – 7), DQM<3> au byte de poids le plus fort (bit 24 - 31).
DQ Bus de données : Les deux composants SDRAM possèdent une organisation de 16 bits. Par
conséquent, le composant de gauche est connecté aux 16 bits de données de poids plus
faibles (low halfword) et celui de droite aux 16 bits de données de poids plus fort (high
halfword).
Tableau 7 : Signaux de contrôle destinés au composant flash
3 La programmation en assembleur
3.1 Introduction
Ce chapitre traite la programmation en assembleur de façon générale. Toutefois, les exemples d’assembleur y
sont définis avec le jeu d’instruction ARMV5TE des processeurs XScale. La syntaxe de l’assembleur s’oriente à
celle de GNU. Cette dernière diffère dans un certain nombre de point par rapport à celles des autres fournisseurs.
L’assembleur est un langage de programmation proche du hardware. Il dépend par conséquent du hardware, c’est
à dire qu’il a été développé pour une famille de processeur bien définie.
Avantage : l’assembleur est optimisé pour le hardware (registre, architecture etc.)
Désavantage : l’assembleur n’est pas portable, c. à d. qu’il ne peur pas être utilisé avec un autre CPU.
Abstraction
LDR r4,beer Assembler
SUB r4,#1
STR r4,beer
0xe59f4020 Code machine
1110’0101’1001’1111’0100’0000’0010’0000
0xe2444001
1110’0010’0100’0100’0100’0000’0000’0001
0xe0802001
1110’0000’1000’0000’0010’0000’0000’0001
a) La langue naturelle est la plus compréhensive pour l’homme. Elle est malheureusement trop floue et trop
redondante. Elle ne convient donc pas pour la programmation.
b) Le pseudo code est souvent utilisé dans la phase de design. Son niveau d’abstraction est très élevé et il n’est
pas standardisé.
c) Les langages de programmation haut niveau possèdent également un niveau d’abstraction élevé. Les
compilateurs traduisent ces langages dans des codes machine, dont l’efficacité dépend du langage de
programmation et du compilateur. Les langages de programmation les plus répondus pour les
microprocesseurs sont le C et C++. Les langages de programmation haut niveau sont standardisés (ex. ANSI-
C). Ce qui permet de porter les programmes d’un processeur à un autre, avec plus ou mois d’efforts.
d) Les programmes en assembleur sont très difficilement compréhensible pour l’homme. Toutefois, les
programmes en assembleur sont plus performants et utilisent mieux les ressources que les programmes haut
niveau.
/*************************************************************************
* *
* Projet : NomProjet *
* *
* *
**************************************************************************
* Programme/Module : NomModule *
* Nom du fichier : NomFichier.68k *
* Version : 1.00 *
* Définition : dd.mm.yyyy * Entête (header)
* Auteur(s) : NomAuteur * (en option)
* -----------------------------------------------------------------------*
* Description : Description brève du module *
* *
**************************************************************************
* Modification: *
* Auteur Date de la modification *
* m.n dd.mm.yyyy *
*************************************************************************/
/************************************************************************* Interface du
* PUBLIC DECLARATIONS * module
*************************************************************************/
(en option)
.global mysub @ exported subroutines
/************************************************************************* Code
* CODE *
*************************************************************************/
.text
.end
Les entêtes (Header) dépendent de la société. Il existe normalement dans les sociétés des lignes directives pour la
programmation, qui définissent l’aspect de l’entête. L’entête devrait toutefois contenir au minimum les
informations suivantes
• Nom du projet
• Brève description du module
• Historique
La partie « PUBLIC DECLARATIONS » contient tous les noms de sous-routines et de variables, qui sont
définies dans le module courant et doivent être exportées dans les autres modules.
La partie « EXTERNAL DECLARATIONS » contient tous les noms de sous-routines et de variables, qui doivent
être importées depuis les autres modules.
Les fichiers, qui contiennent la description des registres du contrôleur, peuvent être importés dans la partie
« include DEFINITIONS ». include copie tout le contenu du fichier en question dans le module.
La partie « EQUATE DEFINITIONS » contient les définitions EQUATE (correspond à la directive #define
de C).
La partie « VARIABLE AND CONSTANT DEFINITIONS » contient les définitions des variables et des
constantes.
La partie « CODE » contient les instructions du programme. Cette partie est divisée en 4 colonnes (voir chapitre
4.3.1).
Etiquette :
Le champ des étiquettes commence à la première colonne et se termine avec « : ». L’étiquette peut contenir le
nom d’une variable, d’une sous-routine ou d’un label. Un label est une sorte de marque, qui est utilisé pour les
sauts de programme.
Opération / instructions :
Le champ des opérations / instructions contient les instructions pour la CPU ou les directives pour l’assembleur.
Dans l’exemple ci-dessus « LDR » est une instruction, qui commande à la CPU de copier la valeur constante
« MAX » dans le registre « R0 ».
Toutes les instructions d’un processeur sont définies dans son jeu d’instruction (voir chapitre 4, qui traite le jeu
Exemple :
Ce concept permet d’économiser de nombreux sauts de programme en comparaison avec d’autre famille
d’assembleur. La condition est définie avec un suffixe ou un code de condition à la fin de l’instruction. Par
exemple, le suffixe « NE » de la deuxième comparaison a pour conséquence que cette instruction n’est exécutée
que si le résultat de l’opération précédente est différent de zéro. De la même façon, l’addition n’est exécutée que
si le résultat de l’opération précédente est égal à zéro (EQ).
L’instruction, qui vient d’être exécutée, fixe la condition. Cette dernière est évaluée l’aide des bits d’état courant
du programme (flags), qui se situent dans le registre du même nom (Current Program Status Register abrégé par
CPSR). Dans l’exemple de ci-dessus les conditions sont fixées par les deux opérations de comparaisons.
Toutes les opérations arithmétiques, qui sont exécutées par l’ALU, influencent les bits d’état N, Z, C et V. Les
opérations logiques ne modifient que N, Z et C.
Les opérandes :
Les opérandes des instructions ou des directives assembleur sont définis dans le champ des opérandes. Dans
l’exemple de ci-dessus les opérandes de l’instruction « LDR » sont la constante « MAX » et le registre « r0 ».
Le processeur ARM travail fondamentalement avec trois opérandes. Certaines instruction, comme par exemple
les sauts de programme, se limitent à un ou deux opérandes. Le jeu d’instruction ARM ne contient pas
d’instruction sans opérande.
Exemple :
B start
SWI 0x10
Exemple :
MOV pc,lr
LDR r0,waleur
Dans cette expression « dest » représente le registre de destination et « src » celui de source. Les registres r0
à r15 peuvent être utilisés pour cela. L’opérande « shift » permet de traiter un opérande à l’aide du « barrel
shifter » avant l’exécution de l’opération. Il permet également de définit un valeur constante de 8 bits.
Chez les processeurs RISC, les instructions de traitement de données ne se limitent en générale à des opérations
entre registres. Les opérations entre des registres et la mémoire de travail sont exclusivement des opérations de
transfert de données.
Les commentaires :
Les commentaires du champ de commentaire sont définis pour améliorer la compréhension du code.
L’assembleur GNU prévoit trois types de commentaires : les commentaire en bloc, les commentaire en ligne et
les commentaire de fin de ligne.
Les commentaires en bloc sont formulés entre « /* » et « */ », comme en ANSI-C. Les commentaires en ligne
sont définis uniquement sur une ligne et ils sont introduits avec un dièse « # ». Les commentaires de fin de ligne
sont défini à la fin de la ligne et ils sont introduits avec un « @ ». Il est très important de bien commenter les
programmes assembleur. Toutefois, le commentaire ne devrait pas décrire l’instruction, mais améliorer la
compréhension du déroulement du programme.
Exemple :
/* Commentaire de bloc
*/
# Commentaire en ligne
LDR r0,=operand1 @ Commentaire fin de ligne
Les constantes peuvent être spécifié avec le caractère « = ». Cela est valable pour les valeurs numériques et les
adresses, qui sont représentées à l’aide de symbole.
Les valeurs, qui ne peuvent pas être chargées par les instructions « MOV », sont automatiquement adressées de
façon relative avec compteur de programme (ici [pc, #4]). Dans l’exemple suivant, la constante
0x12345678 est chargé de la mémoire programme de cette façon. Par contre, la valeur un est directement
chargée avec l’instruction MOV.
Exemple :
start: LDR r0,=1 @ Charger la valeur constante 1
loop: LDR r1,=0x12345678 @ Charger la valeur 0x12345678
ADD r0,r0,r1 @ Additionner les deux valeurs
B loop
Remarque :
La valeur constant est stockée à l’adresse 0xA00004F8. Le compteur de programme adresse la prochaine
instruction à exécuter, qui est stockée sous 0xA00004EC. Le chargement des instructions relatif au compteur de
programme, s’effectue toujours avec un décalage préalable de - 8. Ce qui fournit le décalage à noter dans le code
suivant : 0xA00004F8 - 0xA00004EC – 8 = 4.
Le Tableau suivant contient les opérateurs, qui sont autorisées dans les expressions assembleur. Des espaces
vides ne sont pas autorisés avant ou après ces opérateurs :
+ Addition
- Soustraction
* Multiplication signée
/ Division signée
% Reste de la division signée (Modulo)
& Opération binaire et
| Opération binaire ou
^ Opération binaire ou exclusive
&& Opération logique et
|| Opération logique ou
==, <> Egalité et inégalité
>, >= Plus grand que, plus grand que ou égale à
<, <= Plus petit que, plus petit que ou égale à
<< Opération de décalage vers la gauche
>> Opération de décalage vers la gauche
- Négation par complément à deux
~ Inversion binaire, complément à un
Tableau 9 : les opérateurs pour les expressions assembleur
Le résultat d’une opération de comparaison est toujours un nombre entier dans l’assembleur GNU. La valeur 0
représente faut et la valeur -1 vrai. Les opérations booléenne && et || fournissent par contre une valeur +1,
lorsque le résultat est vrai. Cette différence ne joue aucun rôle pour l’évaluation des conditions, En effet tous ce
qui est différent de 0 est considéré comme vrai.
Exemple:
Les bits 3 et 11 du registre r0 doivent être effacés.
BIC r0, r0, #(1 << 3) | (1 << 11)
L’instruction BIC efface les bits dans le registre r0. Les bits à traiter sont spécifiés avec le paramètre
« #(1 << 3) | (1 << 11) ». La position du bit y est définie avec l’opérande « << », alors que l’opérande « | »
permet d’associer ces derniers.
• Gestion de projet
La Figure suivante illustre une vue d’ensemble des fichiers, qui sont générés durant la phase de développement :
Editor
Source Source
assembler
Source C / Source
C++
*.s
assembler *.c
C / C++
*.asm *.s *.cpp*.c
*.asm *.cpp
Assembler Compiler
Linker/Locator
3.5.1 L’éditeur
L’éditeur permet de définir le code source en assembleur ou avec un langage de programmation évolué. La
plupart des éditeurs sont sensible à la syntaxe et mettent en évidence les mots clés.
Exemple :
Le code en assembleur suivant a été défini à l’aide d’un éditeur.
.arm
.global start
.text
start: LDR r0,=1 @ Load constant value 1
loop: LDR r1,=0x12345678 @ Load constant value 0x12345678
ADD r0,r0,r1 @ ADD both values
B loop
.end
3.5.2 L’assembler
L’assembleur est toujours spécifique à la famille des processeurs. Si vous voulez développer du code pour des
processeurs différents, vous devez également utiliser des assembleurs différents. On parle de « Cross
Assembler », lorsque le code en assembleur est développé sur un ordinateur différent de celui, qui doit exécuter
le programme (ex. l’assembleur fonctionne sur un PC et le code généré sur le microcontrôleur d’une machine à
café).
L’assembleur traduit le code source assembleur, qui est contenu dans un fichier ASCII avec une terminaison
« .s » ou « .asm », en un fichier objet (.o). Des fichiers supplémentaires peuvent être générés, en fonction des
options de l’EDI (ex. fichier de listing : List-File avec une terminaison « .lis »). Le listing de l’assembleur pour
l’exemple ci-dessus a été illustré dans la Figure suivante :
1 2 3
ARM GAS EditionExample.s page 1
1 .arm
2. .global start
3
4 .text
5 0000 0000A0E3 start: LDR r0,=1
6 0004 04109FE5 loop: LDR r1,=0x12345678 @ ADD 0x12345678
7 0008 010080E0 ADD r0,r0,r1
8 000c FCFFFFEA B loop
9
10 0010 78563412 .end
DEFINED SYMBOLS
EditionExample.s:1 .text:00000000 $a
EditionExample.s:5 .text:00000000 start
EditionExample.s:6 .text:00000004 loop
EditionExample.s:10 .text:00000010 $d
NO UNDEFINED SYMBOLS
Nr. de Contenu
colonne
1 Numéro de ligne du code source
2 Adresse relative de départ du code
3 Code machine, par exemple "0000A0E3" veut dire que le registre r0 doit être chargé avec la
valeur 0. "0100080E0" veut dire que le contenu du registre r1 doit être additionné au registre
r0. Toutes les instructions nécessitent 4 bytes (1 mot) de stockage. Elles sont donc stockées à des
adresses mot (divisibles par 4).
reste Code original en assembleur
Tableau 11 : Colonnes du fichier listing
3.5.3 Le compilateur
Comme l’assembleur, le compilateur traduit le programme haut niveau, qui est défini dans un fichier source, en
un fichier objet. Les compilateurs C ou C++ sont utilisés généralement pour les applications embarqués.
« elf » est une abréviation pour « Executable and Linking Format ». Ce format est un standard pour la gestion
des exécutables, des codes objets des bibliothèques, du mappage de la mémoire et des informations nécessaires
au débuggeur. Le format elf est entre temps très répandu et il a remplacé conceptuellement le COFF et de
nombreux autres formats de débuggeur.
Le contenu de ce fichier ne devrait pas intéresser le programmeur. Il ne peut pas être lu avec l’éditeur, mais par
contre, il peut être exploité avec les applications « objdump.exe » ou « elfdump.exe ».
Figure 66 : Les informations contenues dans le fichier elf exploitées avec OBJDUMP.EXE
3.5.5 Le débuggeur
Le débuggeur permet de tester le code, en représentant le contenu des registres, des variables et du code sur
l’écran du PC. L’insertion de points d’arrêt (break points) dans le code source et son exécution contrôlée, pas par
pas, font partie des exigences minimales pour un débuggeur.
On parle de « target debugging » lorsque le débogage est exécuté directement sur le système en développement.
Dans ce cas le débuggeur, qui est exécuté sur le PC, est responsable du téléchargement du code de débogage sur
le système en développement et de la communication avec ce dernier. Alors que le processeur du système en
développement n’est responsable que de l’exécution de ce code.
Les environnements de développement modernes (IDE), comme Eclipse ou MS Visual Studio, proposent ces
fonctionnalités.
Les chapitres suivants traitent uniquement le jeu d’instruction. La description de l’architecture ARM et des ses
registres est fournie par les chapitres 2.4 et 2.5.
Ex. : ADD, ADd with Carry, Reverse SuBtract, Exclusive OR, BIt Clear,
Toutes les instructions possèdent un format unique de 32 bits. Cela génère un certain nombre de limitations, qui
ne sont pas très graves :
• Il n’est pas possible de charger des valeurs directes de 32 bits. Mis à part si ces dernières peuvent être
construite avec une valeur (mantisse) de 8 bits et 4 bits de décalage. Toutes les autres valeurs doivent être
chargées à partir de la mémoire programme.
• Les instructions saut de programme ne possèdent qu’un déplacement de 24 bits (offset). Les sauts de
programme relatifs sont ainsi limités à un espace de ± 32 Mégas bytes, par rapport à l’adresse courante. Des
vrais sauts de programme de 32 bits sont réalisés avec le chargement d’une valeur de 32 bits, dans le registre
du compteur de programme.
• Les instructions Load/Store ne peuvent fonctionner qu’avec un offset de 12 bits. Les accès aux variables
relatifs sont ainsi limités à un espace de 4 kilos bytes, par rapport à l’adresse courante.
Instruction Fonction
ADC Addition de deux valeurs de 32 bits avec la retenue
ADD Addition de deux valeurs de 32 bits
AND Opération logique ET entre deux valeurs à 32 bits
B Saut relatif ± 32 Mégas bytes
BIC Effacer les bits d’une valeur 32 bits
BKPT Point d’arrêt software (break point)
BL Saut avec stockage de l’adresse de retour (appel de sous-routine)
BLX Saut avec stockage de l’adresse de retour et changement de registre
BX Saut avec changement de registre
CDP CDP2 Opération coprocesseur
CLZ Compter les zéros, qui précèdent
CMN Comparaison inversée de deux valeurs 32 bits
CMP Comparaison de deux valeurs 32 bits
EOR Opération logique OU exclusive entre deux valeurs 32 bits
LDC LDC2 Charger le coprocesseur avec une ou plusieurs valeurs 32 bits
LDM Charger plusieurs valeurs de 32 bits depuis la mémoire dans les registres ARM
LDR Charger la valeur avec une adresse virtuelle dans un registre ARM
MCR MCR2 MCRR Charger une ou plusieurs registres ARM dans le coprocesseur
MLA Multiplication et accumulation d’une valeur 32 bits
MOV Charger la valeur 32 bits dans un registre
MRC MRC2 MRRC Charger depuis le processeur dans un ou plusieurs registres
MRS Copier le registre de statut (CPSR ou SPSR) dans un registre ARM
MSR Copier depuis le registre ARM dans un registre d’état (CPSR ou SPSR)
MUL Multiplier deux valeurs de 32 bits
MVN Charger une valeur 32 bits, préalablement inversée, dans un registre
ORR Opération logique OU entre deux valeurs 32 bits
PLD Indique l’adresse de la case mémoire, qui est en train d’être chargée
QADD Addition signée de deux valeurs 32 bits avec limitation du dépassement de capacité
QDADD Addition signée double de deux valeurs 32 bits et limitation du dépassement de capacité
QDSUB Soustraction signée double de deux valeurs 32 bits et limitation du dépassement de
capacité
QSUB Soustraction signée de deux valeurs 32 bits avec limitation du dépassement de capacité
RSB Soustraction inverse de deux valeurs 32 bits
RSC Soustraction inverse de deux valeurs 32 bits avec la retenu
SBC Soustraction de deux valeurs 32 bits avec retenu
SMLAxy Multiplication/addition signées (16x16)+32 = 32 Bit
SMLAL Multiplication/addition larges signées (32x32)+64 = 64 Bit
SMLALxy Multiplication/addition larges signées (16x16)+64 = 64 Bit
SMLAWxy Multiplication/addition signées ((32x16)>>16)+32 = 32 Bit
SMULxy Multiplication signée (16x16) = 32 Bit
SMULWy Multiplication/addition signées ((32x16)>>16)+32 = 32 Bit
STC STC2 Stocker une ou plusieurs valeurs depuis le processeur dans la mémoire
STM Stocker plusieurs registres ARM dans le processeur
STR Stocker des registres à des adresses virtuelles
SUB Soustraire deux valeurs à 32 bits
SWI Interruption software
SWP Echanger les registres ARM avec la mémoire de façon byte ou mot
TEQ Comparer deux valeurs 32 bits
TST Tester un bit dans un registre 32 bits
UMLAL Multiplication/addition larges non singées (32x32)+64 = 64 Bit
UMUL Multiplication large non signée (32x32) = 64 Bit
Tableau 12 : Jeu d’instruction du processeur ARM à partir de la version V5E.
Syntaxe :
Tous les registres de r0 à r15 peuvent être utilisés pour Rd et Rn. L’opérande N permet de pré traiter
facultativement un des opérandes avec le décalage à barilier (Barrel Shifter). N peut être une valeur constante, un
registre ou un registre décalé. L’opération de décalage ne modifie pas le contenu du registre source. Toutes les
valeurs constantes ne sont toutefois pas admises, car ces dernières ne peuvent être composées que de valeurs 8
bits décalées dans un registre 32 bits. Des informations plus détaillées concernant les opérations de décalage sont
fournit au chapitre 4.3.2.
Exemple :
Avant :
r5 = 5
r7 = 8
Après :
r5 = 5
r7 = 20
Les conditions, qui doivent être évaluées pour l’exécution des instructions conditionnelles, sont lues à partir des
bits du registre d’état courant du programme (CPSR). Ces derniers sont normalement fixés par l’instruction, qui
a été exécutée en dernier par le processeur (en générale des opérations de comparaison). La condition est notée
comme suffixe (ou code de condition) après l’instruction. Le Tableau 14 contient les différentes conditions.
Les opérations non comparatives, qui sont exécutées par l’unité arithmétique (ALU), peuvent également
influencer les bits d’état. Dans ce cas il faut ajouter un S au code d’opération. Les opérations arithmétiques
peuvent influencer les bits N, Z, C et V ; les opérations logiques les bits N et Z ; et le décalage à barillet (barrel-
shifter) uniquement le bit C.
Les instructions LO ou HS peuvent être utilisées au lieu de CC ou CS. Ces dernières, comme les instructions LS
et HI, se réfèrent à des opérations de comparaison non signée. Par contre LT, GE, LE et GT se réfèrent à des
opérations de comparaison signée. Le suffixe AL (always) doit être mentionné pour des raisons d’intégralité. Ce
dernier existe de façon formelle et représente la condition standard en absence de suffixe. Toutefois l’assembleur
gcc ne l’admet pas comme suffixe en présence d’instructions.
Exemple :
Définition du GGT (a, b), qui peut être défini dans un langage de programmation évoluée à l’aide d’une
division en chaîne de la manière suivante :
while (a != b)
if (a > b)
a -= b;
else
b -= a;
Un première implémentation en assembleur ARM avec les registres r1 et r2 pour le stockage respectif de a et
b:
Cette solution est basée sur des sauts de programme. A chaque saut il faut vider le pipeline des instructions, ce
qui ralenti fortement le temps d’exécution du programme. Les instructions conditionnelles permettent de réduire
ce temps d’exécution. De plus le programme devient plus court.
Le registre de compteur de programme peut être utilisé comme registre de destination avec toutes les opérations
ALU. Les processeurs ARM soutiennent les formats petit et gros boutiste (little and big endian). Le standard est
cependant petit boutiste.
clairement structuré que celui d’ARM. Le chapitre 6 traite ce sujet plus en détails.
Le coprocesseur CP15 est destiné au contrôle du système. Il permet de gérer la mémoire cache, la mémoire
externe (MMU), les opérations d’écriture et de lecture à l’aide de tampon et d’autre option du système.
Les processeurs, qui soutiennent le VFP, contiennent le CP10 pour les opérations arithmétique à virgule flottante
de précision simple et le CP11 pour les opérations de précision double. L’ARM V5TE, qui est utilisé avec la
carte de développement CARME, ne possède pas de coprocesseur à virgule flottante.
<MOV|MVN>{<cond>}{S} Rd, N
Le code d’opération MOV charge une valeur alors que MVN en charge le complément. Un suffise <cond>, qui
correspond au code de condition fourni au Tableau 14, peut être ajouté pour des réalisations conditionnelles.
Quand au suffixe S, il permet à l’opération de changer les bits d’état courant du processeur. L’opérande Rd
représente toujours le registre de destination, alors que l’opérande N peut représenter soit le contenu d’un registre
(éventuellement décalé) ou une valeur constante.
Exemple :
MOV r0, r1 @ r0 := r1
MOVS r0, #10 @ r0 := 10, fixe les bit d’état courant du programme
MVN r1, r0 @ r1 := ~r0
MOV pc, lr @ retour depuis la sous-routine
barilier. La valeur de cet opérande peut ainsi être déplacée de 0 à 32 bits vers la gauche ou vers la droite, en
fonction de l’instruction. L’opération de décalage est toujours réalisée en un seul cycle d’horloge,
indépendamment du nombre de bits du décalage. La valeur originale du registre Rm n’est pas changée.
L’ALU traite effectivement la valeur contenue dans le registre Rn et celle fournie par le décalage à barilier N.
Rn Rm
Barrel-Shifter
Zwischenresultat N
ALU
Rd
Certaines instructions de traitement de données comme MUL, CLZ ou QADD ne sont pas en mesure d’utiliser le
décalage à barilier. Dans ces cas, la valeur du registre Rm ne peut être utilisé que de façon directe.
Exemple :
Avant l’opération arithmétique, le contenu du registre Rm est décalé de deux bits vers la droite (LSR). Ce qui
correspond à une division par 4 de la valeur du registre. L’opération de décalage est équivalent à l’opérateur >>
du langage de programmation C.
Avant :
r0 = 0x11
r1 = 0x22
Après :
r0 = 0x8
r1 = 0x22
Le décalage à barilier connaît 5 sortes d’opération de décalage. Ces derniers sont fournis dans le Tableau 16 :
La syntaxe du décalage permet de définir le nombre de bit à décaler soit sous forme direct #shift ou à l’aide
d’un registre Rs, qui contient le nombre de bit du décalage. Le Tableau 17 illustre pratiquement toutes les
formulations possibles.
Exemple :
L’opération MOVS copie le contenu du registre r1, préalablement décalé de un bit vers la gauche, dans le
registre r0. Du fait que l’instruction contient le suffixe S, elle est en mesure de changer le contenu du registre
d’état courant du programme. Le bit 31 du registre r1 est ainsi copié dans le bit de carry.
Avant :
CPSR = nzcvqift_USER (Majuscule: mise à un, minuscule: mise à zéro)
r0 = 0x00000000
r1 = 0x80008001
Après :
CPSR = nzCvqift_USER
r0 = 0x00010002
r1 = 0x80008001
n z c v 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
cpsr r1
r0
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0
n z c v 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Figure 68 : L’instruction MOVS avec une opération de décalage LSR #1. Le bit 31 est copié dans le bit de carry
du registre d’état courant du processeur, car l’instruction MOV a été définie avec le suffixe S.
L’adresse de la case mémoire peut être définie de plusieurs manières. Dans les cas les plus simples, cette
dernière peut être une constante ou un registre. Les modes d’adressage sont traités plus en détails dans le chapitre
suivant.
Les instructions LDR D et STRD sont des extensions du jeu d’instruction DSP. Ces dernières permettent
respectivement de charger et de stocker le contenu de deux registres juxtaposés. La condition pour Rd est un
registre avec un numéro paire. Par exemple, l’instruction STRD avec Rd = r2 stockent le contenu des registres
r3 et r4 dans la mémoire.
Exemple :
Les instructions LDR et STR permettent respectivement de charger et de stocker les données dans la mémoire.
La condition pour l’adressage de la case mémoire est son alignement avec les types de données à transférer.
C'est-à-dire que cette dernière doit être modulo 4 pour les accès mot et paires pour les accès demi mot.
Selon la formule de lecture ARM, les modes d’adressage sont qualifiés de « méthode d’indexation ». Il existe
fondamentalement trois méthodes d’indexations. Ces dernières peuvent être complétés avec des nombreuses
variantes de calcul du déplacement (offset).
Dans la méthode pré indexation, l’adresse de la case mémoire est obtenue par addition du contenu du registre de
base avec le déplacement. Le contenu du registre de base reste constant durant toute l’opération de transfert.
Cependant, si l’on défini une récriture en retour (write back) avec « ! », la nouvelle adresse est copié dans le
registre de base avant le transfert de données.
La méthode post indexation reprend également la nouvelle adresse, mais après le transfert de données.
Les méthodes post et pré indexation avec write back sont surtout utilisées pour les accès aux Tableaux (array).
Quant à la méthode pré indexation, elle est souvent utilisée pour accéder aux structures de données.
Exemple :
L’exemple suivant montre les accès avec les modes d’adressage selon le Tableau 19, avec les valeurs des
registres avant et après l’exécution du transfert de données. Toutes les instructions possèdent la même situation
initiale.
Avant :
r0 = 0x000000000
r1 = 0x000100000
mem32[0x00010000]=0x10101010
mem32[0x00010004]=0x20202020
Après :
r0 = 0x20202020
r1 = 0x00010004
Pré indexation :
LDR r0,[r1,#4]
Après :
r0 = 0x20202020
r1 = 0x00010000
Post indexation :
LDR r0,[r1],#4
Après :
r0 = 0x10101010
r1 = 0x00010004
Il existe de nombreuses variantes pour le calcul du déplacement (offset) : des valeurs direct, des contenus de
registre ou des contenus de registre décalés. Tableau 20 fournit une vue d’aperçu des modes d’adressage pour les
accès du type mot ou byte non signés.
Des limitations existent pour les autres instructions. Par exemple, les instructions STRSB, STRSH, STRH et
STRB ne peuvent pas utiliser le décalage à barilier (barrel-shifter). De plus le déplacement est limité à +/- 8 bits
autour de l’adresse de base.
Exemple en conclusion :
str r1, [r2, r4]! @ stock r1 in [r2+r4] and copy the base address r2+r4 in r2
str r1, [r2], r4 @ stock r1 in [r2] and copy the base address r2+r4 in r2
ldr r1, [r2, #16] @ load r1 with [r2+16]. r2 will not change
Exemple :
L’instruction SWP charge la donnée depuis la case mémoire 0x0001A000 dans le registre r0 et copie le
contenu du registre r1 dans la case mémoire 0x0001A000.
Avant :
r0 = 0x555555555
r1 = 0xAAAAAAAAA
r2 = 0x0001A0000
mem32[0x0001A000]=0x10101010
Après :
r0 = 0x10101010
r1 = 0xAAAAAAAAA
r2 = 0x0001A0000
mem32[0x0001A000]=0xAAAAAAAA
r0 0x55555555
0x10101010
r1 0xAAAAAAAA
0x19FFC
r2 0x0001A000
0x10101010
0xAAAAAAAA
r3
0x1A004
r4
0x1A008
r5
0x1A00C
r6
0x1A010
r7
0x1A014
r15
Le mode d’adressage définit l’accès mémoire selon le Tableau 23. Il est possible de travailler avec des adresses
croissantes (incrémentation) ou décroissantes (décrémentation). De plus, le calcul de la nouvelle adresse peut
être effectué avant ou après le transfert des données. La nouvelle adresse peut également être écrite dans le
registre Rn, comme la pré indexation avec l’écriture en retour (write back) !
Le registre Rn contient l’adresse mémoire, ou les données ont été lues ou déposées dans la mémoire. Si le
registre a été défini avec un opérateur d’écriture en retour (!), la nouvelle adresse sera écrite dans le registre,
comme avec les instruction LDR et STR. Le bloc de registre est défini entre accolades. Ce dernier contient tous
les registres, dont le contenu doit être transféré, soit par énumération avec des virgules ({r0, r3, r6}) ou
sous forme de domaine avec un trait d’union ({r0-r3}). Il n’y a pas de règle concernant la définition par
énumération des registres. Cependant, les registres avec les numéros les plus bas sont transportés en premier.
Lorsque cette instruction est exécutée dans un mode privilégié, l’opérateur « ^ » permet d’accéder aux registres
appartenant au mode utilisateur. La condition pour cela est de ne pas mentionner le registre pc et le bloc des
registres.
Exemple :
Les exemples suivants montrent l’effet des quatre modes d’adressage du Tableau 23, pour le stockage des
registres r0, r4 et r1 dans la mémoire à partir de l’adresse 0x00010000.
0x0000FFF0 0x0000FFF0
r9end r9end r0
r0 r1
r1 r4
r9start r4 0x00010000 r9start 0x00010000
Exercice :
Le bloc des registres r0 à r4 doit être stocké dans la mémoire à partir de l’adresse 0x0001A000, dans un
ordre croissant. Le registre r6 doit être utilisé comme pointeur et il doit contenir, à la fin de l’instruction, la
dernière adresse mémoire. C'est-à-dire qu’il doit adresser le contenu du registre r4.
Solution :
Premièrement, il faut utiliser l’opérateur d’écriture en retour (!), afin que r6 contienne la dernière adresse après
l’exécution de l’instruction. Ensuite, il faut choisir le mode incrémenter avant, pour que r6 adresse le dernier
élément. Finalement, il faut soustraire 4 à l’adresse mémoire, afin que le programme écrive le contenu du
premier registre à l’adresse 0x0001A000.
r0
r1
LDR r6,0x0001A000-4 0x00019FFC r6 start
STMIB r6!,{r0-r4} r2
0x0001A000 r0
r3
0x0001A004 r1
r6start 0x0001A000-4 r4
0x0001A008 r2
r0 0x0001A000 r5
0x0001A00C r3
r1 r6 0x0001A000-4
0x0001A010
0x0001A010 r4 r6end
r2 r7
0x0001A014
r3
r6end r4 0x0001A010 r15
Les architectures ARM permettent de conFigurer librement le fonctionnement de la pile. Par exemple, la pile
peut croître soit de façon ascendante (vers les adresses les plus hautes) ou de façon descendante (vers les
adresses les plus bas). Le mode d’adressage du pointeur de pile est également configurable. Ce dernier peut
adresser soit le dernier élément stocké, en mode plein (Full), ou la prochaine case libre, en mode vide (Empty).
La pile devrait être configurée soit de façon conventionnelle ou en fonction de codes déjà existants. Le standard
d’appel de procédure ARM-Thumb (ARM-Thumb Procedure Call Standard ≡ ATPCS) recommande d’utiliser la
pile en modes décroissant et plein (Full Descending). Les opérations « push » sont ainsi réalisées avec STMFD et
celles « pop » avec LDMFD (voir Tableau 24).
Le Tableau 24 illustre les différentes opérations push et pop en fonction des modes de configuration introduit ci-
dessus :
STMFD sp!,{r1,r2}
Vorher: r1=0x00000005 Nachher: r1=0x00000005
r2=0x00000006 r2=0x00000006
sp=0x00020014 sp=0x0002000C
Exemple :
L’opération push avec STMED a pour conséquence, que le pointeur de pile adresse la prochaine case vide de la
pile.
STMED sp!,{r1,r2}
Vorher: r1=0x00000005 Nachher: r1=0x00000005
r2=0x00000006 r2=0x00000006
sp=0x00020014 sp=0x0002000C
Status Register ≡ PSR). L’instruction MSR permet d’écrire dans les registres CPSR (current program status
register) et SPSR (saved program status register). Inversement l’instruction MRS permet d’écrire le contenu des
registres CPSR et SPSR dans la mémoire.
L’étiquette « field » définie la zone du registre CPSR, dans laquelle if faut écrire (voir Figure 69). field
peut être une combinaison des caractère suivants : Control (c), Extension (x), Status (s) et Flags (f). Donc Par
exemple : CPSR_c ou CPSR_cf.
Bit 31 30 29 28 27 7 6 5 4 0
Exemple :
Contrairement aux nombreux autres types de processeurs, les processeurs ARM ne possèdent pas d’instructions
pour libérer ou masquer les interruptions. Cela est réalisé avec la mis à un ou à zéro du bit 6 pour le FIQ et du bit
7 pour le IRQ. La mise à zéro du drapeau I dans le champ de contrôle permet de libérer les interruptions IRQ.
Pour cela il faut d’abord sauver le contenu du registre CPSR dans un registre de travail (dans cet exemple r1).
Ensuite il faut effacer le bit 7. Et finalement il faut transférer le nouvel état dans le registre CPSR.
Avant :
CPSR = nzcvqIFt_SYS
Après :
CPSR = nzcvqiFt_SYS
Cet exemple part du principe que le processeur se trouve dans un mode privilégié (dans cet exemple le mode
SYS). Dans le mode utilisateur, le processeur est capable de lire le contenu du registre CPSR, mais il ne peut
changer que les bits du champ des drapeaux (les bits 24 à 31).
cp représente le numéro du coprocesseur. Ce dernier se situe entre cp0 et cp15. Le coprocesseur 15 (CP15) est
réservé pour la configuration du système. Ce qui comprend la gestion de la mémoire et du cache. Les registres
cn, cm et cd sont les registres internes du coprocesseur. L’opération effective du coprocesseur est définie avec
le code d’opération opcode. Ce dernier est spécifique au coprocesseur.
Instruction Description
LDC STC LDC2 STC2 Transfert de données entre le coprocesseur et la mémoire
MRC MCR MCR2 MRC2 Transfert de données entre le coprocesseur et les registres
CDP CDP2 Exécution de l’instruction coprocesseur
Tableau 25 : Aperçu des instructions coprocesseur destinées aux transferts de données
et à démarrer les actions du coprocesseur.
Il existe quatre modes d’adressage, avec lesquels le coprocesseur peut accéder à la mémoire. Ces derniers sont
fournis dans le Tableau 26.
Exemple
Le registre c0 du CP15 contient le numéro d’identification du coprocesseur. Ce dernier est transféré depuis le
CP15 dans le registre de travail r0.
Avant :
r0=0x00000000
MRC p15,0,r0,c0,0
Après :
r0 = 0x69054117
Interprétation du résultat pour les bord de développement CARME : Les bit [24:31]=0x69 spécifient le
fabriquant Intel. Les bits [16:23]=0x05 représente l’architecture et la variante 5TE. Les bits [4:15]=0x411
correspondent au numéro d’article (Implemented Defined Part Number) et les bits [0:3]=0x7 le numéro de la
révision (Implemented Defined Revision Number). Référence : ARM Architecture Reference Manual, p. B2-7:
Main ID Register – Post ARM7 Processors.
Les instructions de comparaison et de test représentent des cas spéciaux d’opérations logiques. Ces derniers
modifient les bits d’état du registre CPSR. Les bits d’état constituent les bases de décision pour l’exécution des
instructions conditionnelles.
La spécialité des processeurs ARM est que leurs instructions arithmétiques et logiques ne modifient pas ses bits
d’état courant. Toutefois, cela peut être forcé avec le suffixe « S », qu’il faut ajouter à la fin de l’instruction. Les
opérations étendues, comme par exemple les instructions DSP (QADD, SMLA etc.) qui ne font pas partie du jeu
d’instruction du noyau, ne peuvent pas être complétées avec ce suffixe. Elles ne sont donc pas en mesure de
modifier les bits d’état.
Instruction Description
ADD Adition 32 bits
ADC Adition 32 bits avec report de la retenu
AND Opération logique ET
BIC Effacer un bit (NICHT UND)
CMP Comparaison
CMN Comparaison inverse
EOR Opération logique OU exclusive
MUL Multiplication 32 bits
MLA Multiplication et addition 32 bits
ORR Opération logique OU
QADD Addition 32 bits avec protection contre les dépassements de capacité (DSP)
QDADD Addition 32 bits avec protection contre les dépassements de capacité (DSP)
QDSUB Soustraction 32 bits avec protection contre les dépassements de capacité (DSP)
QSUB Soustraction 32 bits avec protection contre les dépassements de capacité (DSP)
RSB Soustraction 32 bits inverser
RSC Soustraction 32 bits inverser avec report de la retenu
SMLA Multiplication 16 bits / addition 32 bits signées (DSP)
SMLAL Multiplication 16 bits / addition 64 bits signées (DSP)
SMLAW Multiplication 32x16 bits / addition signées (DSP)
SMUL Multiplication 16 bits signée (DSP)
SMULW Multiplication 32x16 bits signée (DSP)
SUB Soustraction 32 bits
UMLULL Multiplication 32 bits non signée
Tableau 27 : Aperçu des instructions arithmétiques et logiques.
Cette famille comprend également celles de comparaison et de test.
L’opérande N peut être une valeur direct (constante avec #), le valeur d’un registre ou le contenu d’un registre
décalé. Les résultats 64 bits sont toujours déposés dans une paire de registres.
Les suffixes <x> et <y> peuvent être soi B ou T. Ces suffixes limitent les bits à traiter du registre : B au 16 bits
de poids plus faibles [0 - 15] et T au 16 bits de poids plus fort [16-31]. Ici <x> se réfère au registre Rm et <y> au
registre Rs.
Certaines instructions DSP possèdent un préfixe Q, qui spécifie un traitement avec de l’arithmétique saturée. Ce
qui permet d’éviter les dépassements de capacité. Dans ce cas, lorsqu’il y a dépassement de capacité, le résultat
est la valeur maximale ou minimale qui peut être représentée avec 32 bits.
Du fait que l’opérande N peut avoir plusieurs formes, il est logique de pouvoir réaliser la soustraction également
à l’envers. Les instructions de soustraction à l’envers RSB et RSC sont à disposition pour cela. Les instruction
CMP, CMN, TEQ, TST réalisent respectivement les opérations SUB, ADD, EOR et AND, mais elles n’en stockent
pas le résultat dans un registre. Les instructions MUL et MLA ont la limitation suivante : le registre Rd ne peut pas
être pc ou Rn.
Seules les opérations de comparaison sont en mesure de modifier les bits d’état courant du programme, contenus
dans le registre CPSR. Les opérations arithmétique ne peuvent donc par modifier ces bits par défaut. Toutefois,
si cela devait être le cas, il faudrait leur ajouter un suffixe « S » à la fin.
Le jeu d’instruction ARM ne prévoit pas d’opérations de division. Ces dernières doivent donc être implémentées
avec des opérations de décalage et de soustraction. L’arithmétique BCD n’est également pas soutenue de façon
directe par le processeur.
Exemple :
L’instruction SUB permet de réaliser une simple soustraction. Le code suivant soustrait la valeur du registre r2
à celle du registre r1 et stocke le résultat dans le registre r0
Avant :
r0 = 0x00000000
r1 = 0x00000005
r2 = 0x00000002
Après :
r0 = 0x00000003
Exemple :
Le jeu d’instruction ARM ne comprend pas les instructions d’inversion. Cela peut être réalisé avec une
soustraction inversée, en soustrayant la valeur 0 de la manière suivante : –z=0-z.
Avant :
r0 = 0x00000002
RSB 0, r0, #0
Après :
r0 = 0xFFFFFFFE
Exemple :
Une boucle de compteur peut être réalisée facilement avec l’instruction SUBS. Le code suivant décrémente la
valeur du registre r1. Le résultat, dans ce cas nulle, est stocké dans le registre r1. Parallèlement, Le drapeau Z du
registre d’état CPSR est mis à un. Ce qui pourrait être la base pour un saut conditionnel.
Avant :
CPSR = nzcvqIFt_USR
r1 = 0x00000001
Après :
CPSR = nZcvqIFt_USR
r1 = 0x00000000
Exemple :
Les processeurs ARM V5 avec l’extension E possèdent de nombreuses instructions DSP. Cela concerne
également des opérations, qui ne traitent qu’une partie de l’opérande. Le suffixe B permet dans ces cas de
spécifier le demi mot inférieur (bits 0-15) et le suffixe T le demi mot supérieur (bits 16-31).
Avant :
r0 = 0x11111111
r1 = 0x22222222
r2 = 0x33334444
r3 = 0x11112222
Après :
r0 = 0x159E1D95
r1 = 0x22222222
r2 = 0x33334444
r3 = 0x11112222
L’opérande N peut être une valeur direct (constante avec #), la contenu d’un registre ou le contenu d’un registre
décalé. Contrairement aux nombreux autres processeurs, les opérations logiques n’influencent pas par défaut les
drapeaux du registre d’état courant CPSR. Si toutefois cela devrait être le cas, il faut ajouter un suffixe S à la fin
du code d’opération.
Exemple :
L’instruction BIC permet d’effacer certains bits dans un mot, sans en influencer les autres. Dans l’exemple
suivant, tous les bits de r2, dont la valeur correspond à « 1 », sont effacés dans le registre r1, et le résultat est
stocké dans r0.
Avant :
r1=0b1111
r2=0b0101
Après :
r0 =0b1010
Exemple :
L’instruction ORR réalise une opération logique OU. Dans l’exemple suivante, cette opération est réalisée entre
les valeurs des registres r1 et r2, et le résultat est stocké dans r0.
Avant :
r0=0x00000000
r1=0x10305070
r2=0x02040608
Après :
r0=0x12345678
Le jeu d’instruction Thumb possède par contre ses propres instructions de décalage et de rotation. La raison est
que ce jeu d’instruction n’a pas accès au décalage à barillet.
Exemple :
Le contenu du registre r0 doit être déplacé de un bit vers la gauche. Ce qui correspond à une multiplication par
2. L’instruction MOV est la solution la plus simple pour ce problème.
Avant :
r0 = 0x00004321
CPSR = nzCvqIFt_SYS
Après :
r0 = 0x00008642
CPSR = nzcvqIFt_SYS
n z c v 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 0 1 0 0 0 0 1
cpsr r0
r0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 0 1 0 0 0 0 1 0 0
n z c v 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Un saut de programme conditionnel n’est exécuté que si certaines conditions (bit d’état courant) sont réalisées.
Par contre, un saut de programme inconditionnel est toujours exécuté.
Les sauts de programme absolus engendrent des sauts à des adresses fixes. Ce type de sauts n’est pas soutenu par
les processeurs ARM. Les sauts de programme relatifs engendrent des sauts relatifs, c'est-à-dire avec un certain
déplacement (offset) par rapport à l’adresse courante. Ce déplacement est calculé automatiquement par
l’assembleur. Les processeurs ARM soutiennent uniquement les sauts relatifs.
Saut absolu (par exemple Intel x86, Motorola 68x mais pas ARM!)
Départ du programme à l’adresse 0x0000 : Le même programme est démarré à l’adresse 0x2000 :
0x0000
0x0000 prog. Start
...
...
JMP 0x6000 0x2000 prog. Start
... ...
... ...
JMP 0x6000
...
0x6000 instruktion 1
0x6000 instruktion x
0x8000 instruktion 1
Le programme de gauche, qui est exécuté à partir de l’adresse 0x0000 (Figure de gauche), saute à l’adresse
0x6000 après l’instruction « JMP 0x6000 ». Ce qui est juste. Si ce même programme est exécuté à partir de
l’adresse 0x2000 (Figure de droite), le programme saute toujours à l’adresse 0x6000 après l’instruction
« JMP 0x6000 ». Ce qui est faux.
Æ Les programmes, qui contiennent des sauts absolus, ne peuvent pas être déplacés dans la mémoire.
Départ du programme à l’adresse 0x0000 : Le même programme est démarré à l’adresse 0x2000 :
0x0000
0x0000 prog. Start
...
...
B label1 Offset 0x2000 prog. Start
... ...
... ...
B label1 Offset
label1: ...
0x6000
0x6000 instruktion x
0x8000 label1:
Le programme, qui est exécuté à partir de l’adresse 0x0000 (Figure de gauche), exécute un saut relatif (Branch)
d’une certaine distance (offset) à l’étiquette label1. Si ce programme est déplacé dans la mémoire (image de
droite), alors il continuera de fonctionner correctement. Car la distance entre l’instruction « B lable1 » et
l’étiquette lable1 reste constante.
Les programmes, qui contiennent des sauts relatifs, peuvent être déplacés dans la mémoire.
L’étiquette (label) représente, en tant que constante symbolique, l’adresse à laquelle le programme doit sauter.
L’assembleur calcul le déplacement (offset), par rapport au contenu du registre compteur de programme pc.
C’est à dire en tenant compte de l’adresse de l’instruction courante. La destination doit se situer dans une zone
de ± 32 Méga bytes, par rapport à l’instruction courante. Alors que les instruction B et BL nécessitent une
constante symbolique comme adresse de destination ; les instruction BX et BLX peuvent réaliser un saut en se
basant sur le contenu d’un registre. Cela est spécialement utile, lorsque l’adresse de destination est calculée
durant le déroulement du programme ou lue à partir d’un Tableau.
Le Tableau 30 illustre les quatre instructions destinées aux sauts de programme ARM.
L’appelle d’une sous routine (sous programme) est réalisé avec l’instruction BL. BL stocke également l’adresse
de l’instruction, qui suit celle du saut, dans le registre de reliage lr (r14). Cette dernière sert d’adresse de
retour depuis la sous-routine.
La distance de saut maximale est limitée aux 24 bits de codage pour l’offset. Ce qui correspond à environ ± 32
Méga bytes.
Au changement dans le mode Thumb, avec une adresse de destination dans le registre Rm, l’adresse de
destination doit être incrémentée (+1). Les processeurs ARM ignorent le bit de poids plus faible, car ils réalisent
le saut selon RM & 0xFFFF’FFFE. Ainsi il n’y a pas de changement de mode, lorsque le registre Rm contient
une adresse paire.
Lorsque l’instruction BLX est utilisée avec une adresse directe, le processeur entre dans le mode Thumb sans que
cette dernière ne doive être incrémentée (+1).
Exemple :
Les sauts de programme peuvent être réalisés soit en avant ou en arrière. Le calcul de l’offset est effectué
automatiquement par l’assembleur. L’exemple suivant illustre des sauts non conditionnels. Le premier saut
franchit les trois opérations d’addition et arrive directement à la première soustraction. Le deuxième saut réalise
un retour en arrière et produit ainsi une boucle infinie.
B ahead
ADD r1,r2,#4
ADD r0,r6,#2
ADD r3,r7,#4
ahead:
SUB r1,r2,#4
back:
ADD r1,r2,#4
SUB r1,r2,#4
ADD r4,r6,r7
B back
Exemple :
Les sous-routines sont appelées normalement avec l’instruction BL. Le retour s’effectue avec l’adresse de retour,
qui a été stockée avec BL dans r14. L’exemple suivant illustre le principe des appelles de sous-routines et le
retour à partir de ces dernières.
...
BL subroutine
ADD r0, r1, r2
...
subroutine:
<Subroutine code >
MOV pc, lr @ Return from subroutine
L’assembleur ARM ne prévoit pas d’instruction spécifique pour le retour à partir des sous-routines. Cette
dernières est toujours réalisée soit avec l’instruction « MOV pc, lr », ou avec la lecture de l’adresse de
retour depuis la pile.
<B|BL>{<cond>} label
BX{<cond>} Rm
BLX{<cond>} Rm
L’expression <cond> est le code condition sous forme de suffise selon le Tableau 31. Le reste est identique au
chapitre 4.5.2.
Exemple :
Une sous-routine doit calculer la faculté d’un nombre entier positif. Ce dernier est transmis à la sous-routine
avec le registre r0. La sous-routine doit ensuite enchaîner les multiplications et retourner le résultat avec le
registre r0.
start:
MOV r0, #5 @ Example 5!
BL FacSuB @ Compute (r0!)
B start
loop:
MUL r0, r1, r0 @ multiplication n*(n-1)*(n-2)*...*3*2
SUB r1, r1, #1
CMP r1, #1
BGT loop
endFac:
MOV pc,lr @ return to the calling subroutine
L’instruction SWI engendre un saut de programme à l’adresse 0x00000008, dans le Tableau des vecteurs
d’interruption.
SWI{<cond>} SWI_Number
Le numéro SWI est une valeur entre 0x000000 et 0xFFFFFF. Ce dernier ne concerne pas le processeur mais
le traiteur des interruptions software SWI.
Exemple :
Le code suivante illustre génère un SWI avec le numéro 0x123456, qui est utilisé par le débuggeur.
Avant :
CPSR = nzcvqift_USR
pc = 0x00008000
lr = r14= 0x003FFFFF
r0 = 0x12
SWI 0x123456
Après :
CPSR = nzcvqIft_SVC
SPSR = nzcvqift_USR
pc = 0x00000008
lr = r14= 0x0008004
r0 = 0x12
débuggeur peut traiter cette information et identifier le point d’arrêt avec ce numéro.
BKPT {<immed16>}
CLZ{<cond>} Rd, Rm
Le registre Rm ne peut pas être le registre du compteur de programme pc. L’instruction CLZ retourne la valeur
32 lorsque Rm = 0.
Exemple :
Le nombre de bits zéro, qui précèdent dans une valeur binaire, indique de combien une valeur peut être décalée
vers la gauche, sans qu’il n’y ait un dépassement de capacité.
Avant :
r0 = 0x12
r1 = 0x1234
CLZ r0, r1
Après :
r0 = 0x13
r1 = 0x1234
Exemple :
La séquence d’instruction suivante normalise la valeur contenue dans le registre r0. Le premier bit, qui est
différent de zéro, se trouvera ainsi dans D31. L’exemple admet que la valeur à normaliser est différente de 0.
Avant :
r0 = 0x8421
Après :
r0 = 0x84210000
r1 = 0x10
5.1 Introduction
Les directives de l’assembleur sont des pseudo instructions, qui permettent de gérer l’assemblage du programme.
Contrairement aux instructions réelles, ces dernières ne sont pas traduites en code machine.
Les directives de l’assembleur dépendent de l’assembleur. En générales les fonctionnalités suivantes sont mises à
disposition :
• Contrôle de l’assembleur
• Contrôle du relieur (Sections et leurs adresses de départ)
• Définition des variables et des constantes
• Formatage de fichiers
5.2 Aperçu
Le Tableau suivant fournit un aperçu des directives les plus importantes de l’assembleur :
Exemple :
.arm
... @ 32-Bit ARM Instruction
.thumb
... @ 16-Bit Thumb Instruction
Exemple:
main.s lcd.s
La sous routine InitDisplay est appelée dans le fichier main.s (le chapitre 6 traite les sous-routines de
façon plus détaillée). La sous-routine InitDisplay est définie et exportée avec la directive .global dans le
fichier lcd.s. L’oubli de la directive .global génère des erreurs de relieur.
Pour des raisons de documentation, la directive .extern permet de montrer que le symbole InitDisplay a
été défini dans un autre module. La directive .extern n’est pas nécessaire pour le relieur (linker).
Exemple :
Un programme minimal en assembleur, qui exécute une boucle infinie, a été défini dans le segment .text. Le
code de démarrage doit effectuer un saut à l’étiquette start, après avoir initialisé le hardware.
.arm
.global start
.text
start: B start
5.5 .align
La directive .align permet de s’aligner sur une adresse 32 bits (modulo 4). Cette directive insère par
conséquent zéro à trois bytes. Les processeurs ARM exigent que les instructions et les données du type mot
(word) du programme soient déposées à des adresses 32 bits.
Exemple :
0000 AA55 .byte 0xaa, 0x55
0002 0000 .align @ 2 bytes 0x00 vont être inseres a cet endroi
0004 48616C6C .asciz "Hallo"
6F00
.align AL {, FB}
L’opérande AL permet de spécifier le déplacement. Ce dernier dépend de la plateforme. Par exemple, la directive
« .align 3 » n’a pas la même signification chez les processeurs ARM et i386. Cette dernière correspond
respectivement à un déplacement de huit et zéro bytes. L’opérande FB permet de définir les bytes de
remplissage.
Exemple :
Le code suivant illustre les effets des directives .align. Dans cet exemple, la directive .align 3 permet de
s’aligner sur la prochaine adresse, qui est un modulo de 23 = 8.
Du fait que la directive .align dépend des plateformes, deux nouvelles directives d’alignement ont été
spécifiées par le standard GNU : balign et .p2align. Ces dernières ne dépendent plus des plateformes.
Exemple :
La directive .align 8 engendre un alignement à la prochaine adresse modulo 28 = 256:
.data
0000 55 .byte 0x55
0001 11111111 .align 8, 0x11
11111111
11111111
11111111
11111111
0100 FF .byte 0xff
0101 00000000
5.5.1 .balign
La directive .balign (byte align) fonctionne comme .align, mis à part que le paramètre d’alignement AL
est toujours défini en byte. Ce dernier décrit la valeur modulo, sur laquelle l’alignement doit avoir lieu. La
directive .balign 8 engendre, indépendamment de la plateforme, un alignement sur une adresse modulo 8
bytes.
Les directives .balignw et .balignl sont des cas particuliers de .balign. En effet, ces derniers
considèrent le byte de remplissage comme un demi mot (2 bytes) ou un mot (4 bytes).
Exemple :
La directive .balign peut être utilisée de façon universelle. Le paramètre d’alignement doit toujours être
défini en byte et doit être une puissance de 2. Les autres valeurs engendrent un message d’erreur.
.data
0000 FF .byte 0xFF
.balign
0001 FF .byte 0xFF
.balign 2
0002 FF .byte 0xFF
0003 00 .balign 4
0004 FF .byte 0xFF
0005 000000 .balign 8
0008 FF .byte 0xFF
Exemple :
La directive « .balignw 4,0x1234 » exécute un alignement à une adresse modulo 4. Si un déplacement de
2 bytes a lieu, il sera rempli avec la valeur 0x1234. La déposition des bytes dans la mémoire dépend du mode
boutiste du processeur. Les déplacements de un ou trois bytes engendrent des messages d’erreurs.
.data
0000 FFFFFFFF b1: .byte 0xFF, 0xFF, 0xFF, 0xFF
0004 .balignw 4, 0x1234
0004 FFFF b2: .byte 0xFF, 0xFF
0006 3412 .balignw 4, 0x1234
5.5.2 .p2align
La directive .p2align (power of 2 align) fonctionne comme .align, mis à part que le paramètre de
déplacement AL y est défini sous la forme exponentielle. Cette dernière fournit la valeur modulo, sur laquelle il
faut s’aligner. La directive « .p2align 3 » engendre, indépendamment de la plateforme, un alignement sur
une adresse modulo 8 bytes.
Les cas particulier sont ici .p2alignw et .p2alignl. Ces derniers considèrent les bytes de remplissage
respectivement comme des demi mots et des mots.
Exemple :
La directive « .p2align 3 » se réfère toujours sur des adresses, qui sont des multiples de 8. Les espace de
déplacement sont remplis avec des valeurs nulles.
.data
0000 10 b1: .byte 0x10
0001 00000000 .p2align 3
00000
0008 33 b2: .byte 0x33
Exemple :
Les directives « .p2alignw 2, 0x1234 » génèrent des déplacements sur des adresses, qui sont des
multiples de 4. Les espaces de déplacement du type mot sont remplis avec la valeur 0x1234. La suite des bytes
dépend du mode boutiste du processeur. Les déplacements de un ou trois bytes engendrent des messages
d’erreurs.
.data
0000 FF b1: .byte 0xFF
0001 565656 .p2align 2, 0x56
0004 FFFF b2: .byte 0xFF, 0xFF
0006 3412 .p2alignw 2, 0x1234
Exemple :
0000 48616C6C .ascii ″Hello″
6F
0005 57656C74 .asciz "World!"
2100
5.7 .byte
La directive .byte permet d’insérer des constantes et des variables du type byte dans le code. Plusieurs valeurs
séparées par des virgules peuvent être définies. Ces dernières sont déposées dans le même ordre dans la
mémoire.
La syntaxe de .byte est la suivante :
L’étiquette (label) représente le nom de la constante. Cette dernière est utilisée comme adresse symbolique afin
de pouvoir accéder à la constante. Les valeurs négatives sont stockées en complément à deux. Les valeurs hexa
décimales commencent avec 0x ou 0X, les valeurs octales avec un 0 et les valeurs binaires avec 0b ou 0B. Les
valeurs décimales ne commencent avec un chiffre différent de zéro.
Exemple :
0000 416225 .byte 'A', 'b', 37
0003 20D0 .byte 0x20, -0x30
0005 F608 .byte -10, 010
0007 0A .byte 0B1010
Exemple :
Les directives .2byte et .hword sont des synonymes. Elles déposent les constantes à 16 bits en respectant
l’ordre de leurs énumérations.
.data
0000 1100 h1: .2byte 0x11
0002 33224400 h2: .2byte 0x2233, 0x44
0006 1100 h3: .hword 0x11
0008 33224400 h4: .hword 0x2233, 0x44
Exemple :
Les directives .4byte et .word sont des synonymes. Elles déposent les constantes à 32 bits en respectant
l’ordre de leurs énumérations.
.data
0000 11000000 w1: .4byte 0x11
0004 55443322 w2: .4byte 0x22334455, 0x66
66000000
000c 11000000 w3: .word 0x11
0010 55443322 w4: .word 0x22334455, 0x66
66000000
5.8 .space
La directive .space réserve de l’espace mémoire pour un bloc, contenant un certain nombre de bytes. Ce
dernier est normalement utilisé pour le stockage de données.
Le paramètre AB spécifie le nombre de bytes, qui doivent être réservés pour le bloc. Le paramètre en option FB
défini le byte de remplissage, qui sera utilisé pour tout le bloc.
Exemple :
Les blocs de données sont des bytes, qui sont stockés successivement dans la mémoire. Ces derniers peuvent
également être initialisés. Dans les applications embarquées, l’initialisation de cette zone de mémoire est la tâche
du chargeur de démarrage (boot loader).
.data
00000 00000000 .space 0x10
00000000
00000000
00000000
00010 AAAAAAAA .space 0x10000, 0xAA
AAAAAAAA
AAAAAAAA
AAAAAAAA
AAAAAAAA
10010 00000000 .space 0x1234
00000000
00000000
00000000
00000000
5.9 .include
La directive .include copie du code le contenu du fichier à inclure dans le code source. Cette opération est
effectuée durant le processus d’assemblage du programme. Le fichier à inclure doit se trouver dans le répertoire
courant du projet. C'est-à-dire celui, qui contient les fichiers objets. L’option –I de la ligne de commande permet
de spécifier le chemin d’accès aux fichiers à inclure.
Exemple :
.include "pxa270.s"
Exemple :
1 .text
2 .equ newline, 0xa
3 .set bitmask, 0b10010111
4 TAB = 011
5
6 0000 0A00A0E3 MOV r0, #newline
7 0004 9710A0E3 MOV r1, #bitmask
8 0008 011000E0 and r1, r0, r1
9 000c 0930A0E3 MOV r3, #TAB
10
11 .equ newline, 100 /* Redefinion of newlin */
12
13 0010 6400A0E3 MOV r0, #newline
Les symboles newline, bitmask et TAB sont dotés de valeurs numériques. La programmation peut être
effectuée avec ces symboles au lieu de leurs valeurs numériques. Cette définition centrale augment la lisibilité du
code et facilite son entretient. La définition directe de valeurs numérique dans le code devrait constituer une
exception.
5.11 .org
La directive .org défini une adresse absolue, à partir de laquelle les instructions (ou les données) du programme
doivent être déposées dans la mémoire. La définition des segments avec .org constitue une exception, avec les
processeurs ARM. Cela provient du fait que les sauts de programme y sont toujours réalisés de façon relative.
Exemple :
.org 0xa0000800
mysub: ...
MOV pc,lr
Dans l’exemple de ci-dessus l’adresse de départ de la sous-routine « mysub » est fixée à 0xa0000800. En
règle générale, l’utilisation de la directive .section est beaucoup plus élégante.
5.12 .section
L’espace mémoire des instructions et des données du programme peut être partagé en plusieurs blocs (segments
ou sections). On distingue fondamentalement le segment du code et celui des données. Le segment du code
(.text) contient les instructions du programme, qui sont stockées en générale dans une ROM. Le segment des
données est composé de variables, de la pile (stack) et d’autres types de grandeurs. On distingue ici l’espace
mémoire à initialiser (.data) et celui à ne pas initialiser (.bss). Le gestionnaire d’amorçage (boot loader) est
responsable de l’initialisation de l’espace .data. Dans les programmes C, ce gestionnaire est appelé par le code
de démarrage du module crt0.s.
Plusieurs segments peuvent être regroupés dans une classe. Les classes les plus importantes sont les suivantes :
.text, .data et .bss. Dans ces classes il est possible de définir des sous classes (segments).
Des nouveaux segments ne doivent en générales pas être définis dans des petits programmes. En générale les
classes prédéfinies, .text, .data et .bss, sont suffisent pour ces derniers. La définition des nouveaux
segments est en revanche nécessaire avec les programmes plus complexes, qui utilisent des modules étrangers.
La directive .section permet de définir des nouveaux segments, ou de modifier ceux qui existent déjà. Toutes
les instructions, qui suivent la directive .section, sont affectées à ce dernier. Contrairement à .org,
l’attribution de l’adresse de départ au segment n’est pas absolue. Cette dernière est réalisée par le processus de
reliage (linker).
Dans l’environnement de développement CARME, l’attribution des adresses absolues aux segments .section
s’effectue avec le script de reliage « ldscript_ram ».
Le symbole <NomSection> permet de définir un nouveau segment. Le drapeau "flag" permet de spécifier
les droits d’accès sur le segment. Le Tableau 34 contient les différents drapeaux et leur signification. Les droits
d’accès des classes de bases restent valables en absence de spécification.
Flag Signification
A Segment pour l’allocation de mémoire
W Segment en écriture
X Segment contenant du code exécutable
Tableau 34 : Droit d’accès des segments
Les changements de segment, qui ne sont pas en rapport avec les segments de base, doivent être défini avec la
directive .section. Cette dernière peut être omis avec les classes de base :
.text
.data
.bss
.section .subroutines, "x"
.section .text @ Synonyme to .text
.section .startup
.section MySection, "w"
Exemple :
Lorsque des nouvelles sections sont nécessaires, il faut les définir dans le module assembleur. Dans l’exemple
suivant, deux sous-routines sont définies dans le segment « subroutines » :
.text
start:
BL mysub1
BL mysub2
B start
.section .subroutines
mysub1: MOV pc,lr
.data
b1: .byte 0x22,0x33
msg: .asciz ″Hello″
.section .subroutines
mysub2: MOV pc,lr
L’affectation du segment subroutines aux classes de base (.text, .data ou .bss) est spécifiée dans le
script du relieur « ldscript_ram ». Ce dernier devrait être affecté logiquement à la classe de base .text.
Dans le manuscrit de reliage de la Figure 70, les segments de l’exemple de ci-dessus sont insérés dans les parties
prédéfinies. Il est ainsi possible de spécifier l’ordre, avec lequel ces segments doivent être stockés dans la
mémoire. Il est évident que les modifications du plan de reliage (linkmap) doivent être effectuées avec la plus
grande précaution et en bonne connaissance des choses. Les éventuelles erreurs peuvent provoquer des chutes de
systèmes.
ENTRY(_startup)
MEMORY
{
flash : ORIGIN = 0x00000000, LENGTH = 0x02000000 /* 32MB FLASH */
ram : ORIGIN = 0xA0000000, LENGTH = 0x02000000 /* 64MB RAM */
}
Figure 70: Extrait du manuscrit de reliage pour l’exemple de ci-dessus. Les attributions propres à l’exemple on
été mises en évidence avec des caractères gras.
Les adresses absolues, avec lesquelles les différents objets ont été stockés dans la mémoire, peuvent être
consultées dans le plan de mémoire. Pour cela il faut activer dans les options du relieur sous
« Miscellaneous » les flags « -Wl,-Map=Filename » (voir
Figure 71).
Figure 71: Menu de l’environnement de développement, qui permet d’activer la génération d’un plan de
mémoire (linkmap) pour l’exemple de ci dessus.
Memory Configuration
LOAD startup/crt0.o
LOAD startup/BSP_STARTUP.o
LOAD AsmSectionAnweisung.o
LOAD D:\Programme/CarmeIDE/carme/lib\libBSP.a
START GROUP
LOAD D:/Programme/CarmeIDE/yagarto//lib/gcc/arm-elf/4.1.1\libgcc.a
LOAD d:/programme/carmeide/yagarto/bin/../lib/gcc/arm-elf/4.1.1/../../../../arm-elf/lib\libc.
END GROUP
*(.text)
.text 0xa00000c4 0x0 startup/crt0.o
.text 0xa00000c4 0x7e4 startup/BSP_STARTUP.o
0xa0000598 BSP_STARTUP_init
0xa0000194 UNDEF_Routine
0xa00001a8 DA_Routine
0xa0000180 SWI_Routine
0xa0000834 _sbrk_r
0xa0000214 pxa27x_set_core_freq
0xa0000308 pxa27x_get_freq
0xa0000120 FIQ_Routine
0xa00003d0 InitMemoryController
0xa00001bc PA_Routine
0xa00000c4 IRQ_Routine
.text 0xa00008a8 0xc AsmSectionAnweisung.o
0xa00008a8 start
*(.subroutines)
.subroutines 0xa00008b4 0x4 AsmSectionAnweisung.o
0xa00008b8 _etext = .
0xa00008b8 . = ALIGN (0x4)
*(.text)
5.13 .end
La directive .end indique la fin du code source. Toutes les instructions, qui suivent .end, sont ignorées par
l’assembleur. La directive .end est facultative avec l’assembleur ARM_GNU.
Exemple :
mysub: ...
MOV pc, lr @ dernière instruction
L’assembleur GCC connaît deux opérateurs signés, qui sont équivalent à ceux de C.
Opérateur Descriptions
- Inversion arithmétique (complément à 2)
~ Inversion binaire (complément à 2)
Tableau 36: Les opérateurs signés de l’assembleur
Les opérateurs arithmétiques et logiques sont décrits dans le Tableau 37. La priorité de ces opérateurs est
identique à celle des opérateurs ANSI-C.
Le résultat d’une opération de comparaison est toujours une valeur numérique. La valeur -1 représente ainsi vrai
et celle 0 faux. Toutes les opérations de comparaisons permettent de comparer des valeurs non signées.
Les opérations logiques livrent une valeurs 1 correspond à vrai et celle 0 à faux.
Les calculs arithmétiques ne sont pas possibles si les opérandes se situent dans des sections différentes.
.if <ExpressionLogique>
...
{.else}
.endif
Lorsque la condition de l’expression « ExpressionLogique» est vrai, le bloque .if sera assemblé. Si non
c’est le bloque .else, qui sera assemblé.
Exemple :
La macro suivante permet de décaler a, soit vers la gauche ou vers la droite, en fonction du signe de b.
.macro SHIFTLEFT a, B
.if \b < 0
MOV \a, \a, ASR #-\b
.else
MOV \a, \a, LSL #\b
.endif
.endm
.ifdef <Symbole>
...
{.else}
.endif
Exemple :
Du code supplémentaire peut être inséré dans un programme, afin de pouvoir le tester. Dans l’exemple suivant,
le point d’arrêt n’est exécuté que si le symbole DEBUG a été défini auparavant.
.set DEBUG, 1
start:
LDR r0, =0x80
.ifdef DEBUG
BKPT
.endif
LDR r1, =0x80
.ifndef <Symbole>
...
{.else}
.endif
Le nom doit remplir les critères, qui régissent les noms des symboles. La convention veut que l’on définisse les
noms de macro avec des majuscules. Les arguments représentent formellement les paramètres, comme pour une
fonction C. L’accès à ces paramètres doit s’effectuer dans la macro avec un caractère back slash ‘\’. Une
instruction macro peut être quittée avec la directive .exitm. Cela est toutefois en désaccord avec la
programmation structurée, mais peut dans certain cas simplifier le codage de la macro.
Exemple :
.exitm permet de définir une macro sans la directive .else.
Avant:
r0 = 0x80
r1 = 0x80
.macro SHIFTLEFT a, b
.if \b < 0
MOV \a, \a, ASR #-\b
.exitm
.endif
MOV \a, \a, LSL #\b
.endm
SHIFTLEFT r0, 2
SHIFTLEFT r1, -2
Après:
r0 = 0x200
r1 = 0x20
Exemple :
La macro ADR est une macro prédéfinie pour charger l’adresse des objets. Cette dernière exécute
automatiquement le calcul de l’offset par rapport au registre du compteur de programme pc. La macro peut être
utilisée comme une instruction du processeur.
Exemple :
Le chargement de l’adresse de base d’un Tableau peut être effectué très simplement avec la macro :
.text
...
ADR r0,Tab
...
.data
Tab: .word 0, 1, 2, 3, 4
• .rept (répétition)
• .irp (répétition indéfinie)
.rept <Quantite>
{BlocCode}
.endr
Exemple :
Les mots 0x55AA55AA, 0x00000000, 0xAA55AA55, 0x11111111 doivent être insérés 16 fois dans la
mémoire. La directive .rept permet de réaliser cela de façon compacte :
.rept 16
.word 0x55AA55AA
.word 0x00000000
.word 0xAA55AA55
.word 0x11111111
.endr
Le débuggeur permet de voir, comment ces valeurs ont été déposées dans la mémoire :
Exemple :
.rept permet également de générer un Tableau contenant des valeurs pré calculées à l’aide de symbole.
L’exemple suivant montre la construction d’un Tableau du type byte contenant des valeurs allant de 0 à 255.
Exemple :
La directive .irp permet de répéter un bloc en fonction d’une liste de paramètres. Le code suivant additionne
tous les paramètres au registre r0 à l’aide du registre r1.
.irp param, 1, 2, 3, 4
MOV r1, #\param
ADD r0, r0, r1
.endr
.irp param, 1, 2, 3, 4
MOV r1, #\param
ADD r0, r0, r1
.endr
6.1 Introduction
Les sous routines sont des sous programme en assembleur. Ils correspondent, par conséquent, aux fonctions ou
aux procédures des langages de programmation évolués.
Dans les langages de programmation évolués, le compilateur gère l’appel des sous programmes (callee) à partir
des programmes appelants (caller). Cela n’est malheureusement pas le cas dans les langages de programmation
en assembleur : ici le programmeur est l’unique responsable du déroulement de cet appel. Typiquement, les
problèmes suivants doivent être considérés par ce dernier :
• Appel de la sous routine et retour depuis cette dernière
• Echange de données entre la fonction appelante et la sous routine (transfert des paramètres)
• Les variables locales
subr1:
n. instruction
retour
suivantes.
Dans l’architecture ARM, les appelles de sous routine sont réalisés avec les instructions « Branch-and-Link »
(BL). Cette instruction initialise le compteur de programme (PC) avec l’adresse de départ de la sous routine et
stocke simultanément l’adresse de retour (l’adresse de l’instruction qui suit l’appel de la sous routine dans le
programme appelant) dans le registre de reliage (lr).
ProgrammePrincipale:
BL subr1 @ branch to subr1 and save return address in "lr" (r14)
... @ next instruction
L’instruction « MOV pc, lr » est mise à disposition pour le retour depuis la sous routine. Le contenu du
registre de reliage (l’adresse de retour) est ainsi copié dans le compteur de programme. Cette opération peut
également être définie de la manière suivante : « MOV r15, r14 ».
La sous routine ne devrait pas modifier le contenu du registre de reliage, car ce dernier contient l’adresse de
retour. En particulier, la sous routine ne devrait pas appeler d’autre sous routine, sans sauver préalablement le
contenu du registre de reliage.
=
= Programme principal Sous routine 1 Sous routine 2
subr1 subr2
n. instruction n. Instruction
i t
retour retour
Une sous routine peut s’appeler elle-même, générant ainsi un code récursif. Avec les codes récursifs, il faut faire
attention à la condition d’arrêt. Si non, la pile (stack) pourrait déborder.
Les adresses de retour doivent être systématiquement déposées sur la pile (stack), avec les appels emboîtés de
sous routines. Pour cela il faut :
a) Définir le domaine de la pile avec un le plan de mémoire
b) Initialiser de la pile
La pile est une structure de donnée dynamique, qui fonctionne selon le principe « Last In, First Out ». Les
opérations suivantes sont mises à disposition pour gérer cette dernière :
• Initialisation
• Push (poser un élément sur la pile)
Un nouvel élément est posé sur la pile avec l’opération « push ». L’élément, qui a été déposé en dernier sur la
pile, peut être recherché avec l’opération « pop ». Ces opérations modifient la taille de la pile.
Début de la pile
1. Elément
2. Elément
3. Elément
Pointeur de pile
push pop
Le pointeur de pile (stack pointer) adresse la fin de la pile, c. à d. l’endroit où la prochaine opération aura lieu.
Le registre r13 sert de pointeur de pile dans les processeurs ARM. Il est également possible de définir « sp » au
lieu de r13.
Par conséquent, il est important que la même version de la pile soit utilisée par le compilateur et le programmeur
en assembleur. Le standard d’appel de procédure ARM (ARM Procedure Call Standard, APCS, voir chapitre
6.5) spécifie pour la pile le mode de fonctionnement « full descending ». Les opérations push sont alors
réalisées avec les instructions STR (sauvegarder uniquement du contenu du registre de reliage) et STMFD ("store
multiple full descending", afin de pouvoir sauver plusieurs registres simultanément). Les opérations pop peuvent
être réalisés soit avec les instructions LDR (recherche uniquement du contenu du registre de reliage) et
LDMFD ("load multiple full descending", afin de rechercher le contenu de plusieurs registre à partir de la pile).
L’instruction STMDB ("store multiple decrement before") peut être également utilisée au lieu de STMFD. Car les
deux instructions sont équivalentes. Pour les même raisons, l’instruction LDMFD peut être remplacée par
LDMIA ("load multiple increment after").
Hauptprogramm:
BL subr1 @ branch to subr1, save return address in "lr" (r14)
… @ next instruction
subr1:
STR lr, [sp, #-4]! @ push lr on the stack
… @ code subroutine 1
BL subr2 @ call subroutine 2
LDR pc, [sp], #4 @ return, pop return adress from stack to pc and increment sp
L’instruction STR décrémente d’abord le pointeur de pile de 4 bytes (la pile croît vers le bas). L’adresse de
retour du registre lr est ensuite déposée sur la pile.
L’opération contraire est réalisée à la fin de la sous routine : L’adresse de retour est d’abord transférée depuis la
pile dans le registre compteur de programme pc. Le pointeur de pile est en suite corrigé de 4 bytes vers le haut.
Le programme retourne finalement dans la routine appelante. C'est-à-dire, qu’il exécute l’instruction qui suit
l’appel de la sous routine. Cette dernière se situe dans l’exemple de ci-dessus à l’adresse 0x2004.
L’initialisation de la pile exige d’une part la définition du domaine d’adressage, et d’autre part l’initialisation du
pointeur de pile. L’extrait de code suivant montre comment cela est réalisé :
Définition:
.set SVC_STACK_SIZE, 0x00000200 @ stack size for supervisor mode
.set MODE_SVC, 0x13 @ supervisor mode
Code de démarrage :
LDR r0, =_stack_top_address @ copy start address of stack into r0
Remarque: Les processeurs ARM sont capables de fonctionner dans différents modes (voir chapitre 2). Chaque
mode possède sa propre pile (stack) avec son propre pointeur de pile. Le segment de code de ci-dessus initialise
par exemple la pile du mode superviseur. Les piles des autres modes (fiq, irq etc.) doivent être initialisés de la
même manière. Vous trouverez le code de démarrage (startup code), qui initialise toutes le piles, dans le fichier
« crt0.s ».
Les instructions pour les appelles et les retours de sous routines sont fournies dans le Tableau suivant :
sp return return
0x3200 Adr 1 Adr 1
sp return
0x31FC Adr 2
sp
0x31F8
Stack a) b) c)
Programme principale :
start: MOV r4, #0 @ initialize loop variable r4
loop: BL subr3 @ branch to subroutine
ADD r4, r4, #1 @ increment loop variable
CMP r4, #10 @ check if 10 loops done
BNE loop @ no Æ branch to label loop
...
Sous routine 3 :
subr3: STR lr, [sp, #-4]! @ push lr on the stack
...
MOV r4, #0 @ modify r4
...
LDR pc, [sp], #4 @ return
Le problème de cette séquence de code est que le contenu du registre r4, qui est utilisé dans le programme
principale pour le stockage de la variable de compteur, est modifié dans la sous routine subr3 !
Dans ce cas la règle est le principe des contributions : Les contenus des registres, qui sont modifiés par la sous-
routine, doivent être sauvés au début et restitués à la fin de cette dernière (exception voir chapitre 6.5). Les
instructions du Tableau 38 sont mises à dispositions pour ces opérations. Par conséquent, dans l‘exemple de ci-
dessus, la sous routine subr3 doit également sauvegarder le contenu du registre r4.
Remarque : L’instruction LDMFD copie directement l’adresse de retour dans le registre pc.
Les fonctions C et les sous routines en assembleur peuvent posséder plusieurs paramètres. Il est donc important
que le compilateur C et le programmeur en assembleur respectent les mêmes directives. Le standard d’appelle de
procédure ARM (ARM Procedure Call Standard, APCS) fixe les règles de transmission des paramètres pour les
processeurs ARM (voir chapitre 6.5) :
Les paramètres sont gérés à l’aide d’une liste. Les 4 premiers paramètres sont transmis avec les registres r0
à r3. Les autres paramètres sont transmis, dans un ordre inversé, à l’aide de la pile.
Call by Value:
Une copie de la valeur originale est transmise en tant que paramètre à l’aide des registres (r0 à r3). Dans ce cas
la sous routine ne pas accéder à la variable originale.
Call by Reference:
L’adresse de la variable est transmise en tant que paramètre à l’aide des registres (r0 à r3). Dans ce cas la sous-
routine peut accéder à la variable originale.
Exemple :
Considérons comme exemple une sous routine « sum », qui possèdent 3 paramètres. Cette dernière peut être
définie da la manière suivante en C :
int op1 = 2;
int op2 = 5;
int res;
main:
ADR r0, op1 @ copy address op1 into r0
LDR r0, [r0] @ read value op1, 1st parameter r0 by value
ADR r1, op2 @ copy address op2 into r1
LDR r1, [r1] @ read value op2, 2nd parameter r1 by value
ADR r2, res @ read address res, 3rd parameter r2 by reference
BL sum
Remarque : Le standard APCS n’exige pas de sauver le contenu du registre r3 à l’aide de la pile (voir chapitre
6.5).
super_sum:
STMFD sp!, {r4, r5, lr} @ push r4, r5 and lr
MOV r4, r0 @ temp = p1
ADD r4, r1 @ temp += p2
ADD r4, r2 @ temp += p3
ADD r4, r3 @ temp += p4
LDR r5, [sp, #12] @ buffer = p5
ADD r4, r5 @ temp += p5
LDR r5, [sp, #16] @ buffer = p6
ADD r4, r5 @ temp += p6
MOV r0, r4 @ copy return value to r0
LDMFD sp!, {r4, r5, pc} @ pop r4, r5 and lr, return
=
= = = = = =
SP = = SK= = SK= =
0x3200 = = léÉê~å = léÉê~å =
= = Ç= = Ç= =
= = = =
= = RK= = RK= NS=
= = léÉê~å = léÉê~å =
= = Ç= = Ç= =
= = = =
= = = = = NO=
SP
= = = = äê= =
0x31F8
= = = = = =
= = = = = =
= = = = êR= U=
= = = = =
= = = = =
= = = = =
= = = = êQ= Q=
= = = = =
= = = = =
= = = = =
= = = = SP = M=
= = = = 0x31EC = =
= = = = = =
6.5 APCS
Le standard d’appelle de procédure ARM (ARM Procedure Call Standard, APCS) fixe l’utilisation des registres
de la CPU. Cette convention est importante, afin que les compilateurs et les assembleurs puissent utiliser les
mêmes conventions pour la transmission des paramètres, la sauvegarde des registres etc.
• Les registres r0 à r3 (a1 à a4) sont utilisés pour transmettre les arguments aux sous routines. Ces dernières
ne doivent pas conserver leur contenu. Par conséquent, ces registres peuvent être utilisés comme registres
scratch (volatiles, utilisables à souhait et qui ne doivent pas être sauvés dans par les sous routine appelée).
Attention : cela veut dire que les fonctions appelantes doivent considérer, que le contenu de ces registres peut
être modifié par l’appelle de la sous routine ! Le registre r0 est utilisé pour le retour d’un résultat à 32 bits.
Les registres r0 et r1 sont utilisés lorsque ce résultat est de 64 bits.
• Les registres r4 à r8 (v1 à v5) et les registres r10 et r11 (v7 et v8) peuvent être utilisés comme registre
de variable (mémoire intermédiaire). Ces registres doivent retrouver leur valeur d’origine à la fin de la sous
routine. Ils doivent par conséquent être sauvés au début de cette dernière (callee-saved), voir chapitre 6.3.
• Le registre r9 dépend de la plateforme. Il peut être utilisé comme registre de base statique (static base, sb)
ou comme registre thread (thread register, tr). Si r9 n’est pas utilisé pour ces tâches, il peut être utilisé
comme registre de variable.
• Le registre r12 (ip, Interlink Pointer) peut être utilisé comme registre scratch entre les routines et les sous
routines.
• Le registre r13 est le pointeur de pile.
• Le registre r14 est le registre de reliage.
• Le registre r15 est le compteur de programme.
Les variables locales sont définies normalement à l’aide des registres r4 à r8, r10 et r11. Lorsque des
variables locales supplémentaires sont nécessaires, il faut stocker ces dernières sur la pile. Dans ce cas une zone
de mémoire, dont la taille correspond au bloc de variables locales, est allouée sur la pile. Un registre de pointeur
de trame (Frame Pointer), qui est normalement le registre r11, est initialisé avec l’adresse de base de ce bloc.
myFunc:
STMFD sp!, {r11, lr} @ push r11 and return address, (1)
SUB sp, sp, #40 @ allocate 10 * 4 bytes local variable space, (2)
MOV r11, sp @ r11 points to first local variable, (3)
...
...
ADD sp, sp, #40 @ free local variable space
LDMFD sp!, {r11, pc} @ pop r11 and lr, return
SP alt
lr
r11 alt
1
1
lokaler
Variablen-
bereich 3
La zone de mémoire contenant les variables locales peut être adressée à l’aide du pointeur de trame r11 et d’un
offset positif. Le programmeur est responsable du calcul correct de cet offset, comme ce fut le cas pour le
transfert des paramètres !
Le pointeur de trame adresse la partie inférieure la zone de mémoire contenant les variables locales. Toutefois,
ce pointeur peut être initialisé afin qu’il adresse la partie supérieur de cette zone. L’offset, pour pouvoir accéder
aux variables locales, doit alors être négatifs. Une autre variante est l’utilisation du pointeur de pile au lieu du
pointeur de trame pour adresser ces variables locales.
Lorsque des variables locales sont utilisées simultanément avec une transmission de paramètre avec la pile (voir
chapitre 6.4.2), les paramètres peuvent également être adressés avec le pointeur de trame au lieu du pointeur de
pile. Remarque : certain compilateur C initialise toujours un pointeur de trame, même si la sous routine n’utilise
pas de variables locales alouées sur la pile. Le code est ainsi un peu plus lent mais, par contre, le calcul des
offsets pour l’accès aux paramètres devient beaucoup plus simple.
Les erreurs de gestion de la pile génèrent toujours des chutes de programme, car les adresses de retour ne sont
plus justes !
Il s’en suit la question : pourquoi utilise-t-on les macros. La réponse est dans le comportement temporel : un
appel de sous routine avec l’instruction « BL » et le retour depuis cette dernière nécessite plus de temps (même si
cela ne concerne que deux instructions). Dans les applications, qui sont très critiques au niveau temps, cette
diminution de la vitesse d’exécution favorise la macro.
Dans des applications standards, il faut utiliser en générale des sous routines. Alors que dans les applications, qui
sont critique au niveau temps, il faut utiliser des macros.
Remarque: Un bon design software et des bons algorithmes sont beaucoup plus efficaces pour la performance du
processeur, que le remplacement systématique des sous routines par des macros.
7.1 Introduction
Les interruptions sont des évènements, qui peuvent survenir pendant l’exécution d’un programme et qui requiert
un traitement par ce dernier. Les interruptions ARM peuvent être partagés en 3 catégories :
1. Les interruptions et les exceptions, qui surviennent durant l’exécution d’une instruction. Ce type
d’interruption ou d’exception apparaît de façon synchrone au déroulement au programme. Cette catégorie
comprend les interruptions et les exceptions suivantes :
− Les interruptions software
− Les instructions non définies (l’instruction à exécuter est inconnue ou destinée à un coprocesseur
indisponible)
− Prefetch Abort (exception générée à cause d’une erreur d’accès mémoire durant sa lecture)
2. Les exceptions, qui apparaissent comme effet secondaire à une instruction. Ce type d’exception apparaît
également de façon synchrone au déroulement au programme. Les exceptions, qui composent cette catégorie,
sont les suivantes :
− Data Abort (exception à cause d’une erreur d’accès mémoire, durant la lecture ou l’écriture d’une donnée)
3. Les interruptions générées de façon externe. Ce type d’interruption apparaît de façon asynchrone au
déroulement du programme. C’est à dire que le programme peut être interrompu à n’importe quel instant. Les
interruptions, qui composent cette catégorie, sont les suivantes :
− Reset (mise sous alimentation, appui de la touche reset ou activation du « watch dog »)
− Les interruptions hardware : Les requêtes d’interruption standard (Interrupt ReQuest et abrégé avec IRQ)
et les requêtes d’interruption rapide (Fast Interrupt Request et abrégé avec FIR).
Lorsque des interruptions ou des exceptions surviennent dans uns système, ce dernier doit appeler des sous
routines pour leur traitement. Dans le cas d’une interruption on parle alors de « routine de
service d’interruption » (Interrupt Service Routine : ISR) et dans le cas d’une exception on parle de « traiteur
d’exception » (Exception Handler).
Le déroulement du programme est identique pour les trois catégories d’interruption : le programme principale est
interrompu et l’exception est traitée par le traiteur d’exception. Une description exacte du déroulement se trouve
aux chapitres 7.6 et 7.7.
Première
instruction
Exception
Instruction
courante
Instruction
suivante
Dernière
instruction
1) Les sous routines sont appelées directement avec les instructions de branchement BL. Ce qui n’est pas le cas
des routines de service d’interruption (ISR). En effet, ces dernières sont appelées par des évènements
externes. Le « Tableau des vecteurs d’exception » (vector table) est mise à disposition ici, pour gérer l’appel
de ces différents traiteurs d’exception (voir chapitres 7.6 et 7.7)
2) Le processeur change de mode, lorsque apparaît une interruption. Ce mode dépend de la nature de
l’interruption (voir chapitre 7.3).
3) Les interruptions possèdent des priorités différentes (voir chapitre 7.3)
4) Les requêtes d’interruption standard et les requêtes d’interruption rapides peuvent être masquées (voir
chapitre 7.5)
La plupart des contrôleurs possèdent des exceptions avec des priorités différentes. Certaines familles de
processeurs, dont celles des processeurs ARM, permettent également de spécifier ces priorités en fonction des
applications.
Chez les processeurs ARM, chaque type d’exception est exécuté dans un mode différent. Par conséquent,
certains registres du processeur ne doivent pas être sauvés dans la routine de service d’interruption. Ces registres
sont appelés des registres fantômes (banked registre).
Le Tableau suivant fournit un aperçu des priorités et des modes de traitement des différentes exceptions :
Exemple :
Le programme principal est interrompu par la requête d’interruption IRQ, dont le niveau de priorité est 4. Durant
le traitement de IRQ par sa routine de service d’interruption « irq_handler », apparaît une requête
d’interruption rapide FIQ, dont le niveau de priorité est 3. Par conséquent irq_handler sera interrompu au
détriment de la routine de service d’interruption rapide « fiq_handler ». La Figure suivante montre le
schéma temporel de cet
Exemple :
Priorité
fiq_handler
irq_handler
Programme
principale Temps
IRQ FIQ
Les interruptions hardware sont toujours générées par des composants hardware périphériques. Par exemple le
timer, l’interface sérielle ou un port d’entrée quelconque. Les éventuelles sources d’interruptions dépendent donc
du processeur et de ses composants périphériques.
Dans les processeurs ARM, les composants périphériques peuvent générer soit un IRQ ou un FIQ. La gestion de
ces requêtes d’interruption est réalisée par un contrôleur d’interruption supplémentaire. Car le PXA270 possède
de nombreus composants périphériques capables de générer des interruptions (voir chapitre 7.8). Le registre de
contrôle des niveaux d’interruption (en anglais Interrupt Controller Level Register et abrégé par ICLR) permet
de définir le niveau de chaque source d’interruption (IRQ ou FIQ).
Les interruptions software sont souvent utilisées pour appeler des fonctions du système d’exploitation. Car ces
dernières font entrer le microprocesseur dans le mode superviseur. Ces fonctions ont ainsi la possibilité
d’accéder à tous les registres.
Il est possible de transmettre un paramètre aux interruptions software avec les processeurs ARM (SWI-number,
24 bits). Ce paramètre permet aux systèmes d’exploitation de définir la fonction à appeler.
La gestion des interruptions peut être réalisée en deux étapes avec le PXA270 :
Dans une première étape, les interruptions IRQ et FIQ peuvent être activées ou désactivées. Cela peut être réalisé
à l’aide de leurs bits de contrôle, qui se situent dans le registre d’état courant du programme CPSR (Current
Program Status Register).
Bit 31 30 29 28 7 6 5 4 0
Function
Condition Interrupt Processor
Flags Mask Mode
Thumb
State
Le bit I permet de gérer l’interruption IRQ : 1 désactive cette interruption et 0 l’active. Le bit F gère
l’interruption FIQ de la même façon.
Dans une deuxième étape, les différentes interruptions peuvent être activées ou désactivées à l’aide du contrôleur
d’interruption. Le registre de masquage du contrôleur d’interruption ICMR (Interrupt Controller Mask Register)
permet de réaliser cela (voir chapitre 7.8).
Interrupt- CPU
Controller
Interrupt- Interrupt-
Quelle I/F-Bit Verarbeitung
ICMR CPSR
L’adresse de base des Tableaux de vecteurs d’exception est normalement 0. Toutefois, du fait que le kit de
développement CARME possède une mémoire flash à cette adresse, ce Tableau y a été déplacé à l’adresse
0xA0000000 :
Dans certaines familles de processeurs, le Tableau des vecteurs d’exception contient les adresses des différents
traiteurs d’exception (exception handler). Le processeur lit alors cette adresse et réalise le saut de programme
dans la routine de service.
Dans d’autres familles de processeurs, comme celles des processeurs ARM, le Tableau des vecteurs d’exception
contient uniquement des instructions. Dans ce cas, lorsque apparaît une exception, l’instruction, qui est stockée
dans son vecteur d’exception, est alors chargée et exécutée (Par exemple : avec IRQ ce sera l’instruction qui
est stockée à l’adresse 0xA0000018).
Le vecteur d’exception contient normalement une instruction de branchement relative, avec une distance de 24
bits. Si le processeur doit réaliser un saut de programme absolu, avec une adresse de 32 bits, il doit copier
indirectement cette adresse dans le compteur de programme.
Le Tableau des vecteurs d’exception de ci-dessus est construit de manière statique. C'est-à-dire que les adresses
ont été définies de façon fixe pendant la programmation, et sont introduites par le relieur (linker) durant la
procédure de compilation.
Lorsque le Tableau des vecteurs d’exception est stocké dans une RAM, il peut être construit de façon
dynamique. Il peut ainsi être initialisé au démarrage du programme et changé durant son déroulement. :
1) Définition des déplacements (offset), c'est-à-dire les différences d’adresse entre les traiteurs et les vecteurs
d’exception.
2) Soustraire deux à ces déplacements.
3) Relier les offsets au code d’opération (opcode) de l’instruction de branchement (0xEA000000).
4) Copier les valeurs obtenues dans le Tableau des vecteurs d’exception
Entrée :
Si des registres supplémentaires sont nécessaires dans le traiteur d’exception, il faut sauver et restituer leur
contenu initial à l’aide de la pile. Par conséquent, un traiteur d’exception possèdera toujours la structure
suivante :
• Sauvegarde des registres affectés
• Exécution de la tâche
• Restitution des registres préalablement sauvés
• Retour au programme principal
irq_handler:
...
<handler code>
...
SUBS pc, lr, #4 @ pc = lr – 4, restore CPSR
L’instruction LDMDF peut également être utilisée, lorsque des registres doivent être sauvegardées dans le traiteur
d’exception :
irq_handler:
SUB lr, lr, #4 @ lr = lr -4, subtract offset for return address
STMFD sp!, {r0-r3, r12, lr} @ save context
...
<handler code>
...
LDMFD sp!, {r0-r3, r12, pc}^ @ restore context, return
Le caractères « ^ » à la fin de la liste des registres de l’instruction LDMDF signifie : qu’avant de charger le
compteur de programme avec la valeur du registre de reliage, il faut restituer l’ancien contenu du registre CPSR,
à partir du registre SPSR. Ainsi l’ancien mode du processeur avec les valeurs initiales des bits I et F (c'est-à-dire
avant l’arrivé de l’exception) peuvent être restitués.
Le déroulement du traitement des exceptions peut être schématisé à l’aide de l’exemple suivant :
Application
instr 1
instr 2
IRQ
instr3 PC = LR - 4
CPSR = SPSR_irq
Retour au mode utilisateur
LR = PC+4
Passage au mode IRQ
SPSR_irq = CPSR Vector Table
PC = 0x00000018
B irq_handler
irq_handler:
instr a
instr b
Dans l’exemple de ci-dessus, les instructions 1 et 2 sont exécutées de façon séquentielle. Un IRQ interrompt
alors le programme principale (l’instruction 2 sera toutefois encore exécutée). La CPU lit ensuite la prochaine
instruction à exécuter dans le Tableau des vecteurs d’exception. Ce qui mène à un branchement dans le traiteur
d’IRQ. Finalement, l’état initial du processeur est rétabli et le programme continu avec l’exécution de
l’instruction 3.
Interrupt Request
Interrupt Latency
Interrupt Recovery
Interrupt Response
Time
Le temps de latence d’interruption (Interrupt Latency) est le temps entre l’apparition de la requête d’interruption
(Interrupt Request) et l’exécution de la première instruction du traiteur d’interruption (interrupt handle) :
Le temps de réponse à l’interruption (Interrupt Response) est le temps entre l’apparition de la requête
d’interruption (Interrupt Request) et l’exécution de la première instruction de sa routine de service (ISR) :
Le temps de recouvrement de l’interruption (Interrupt Recovery) est le temps nécessaire pour revenir dans le
programme principal après exécution de la routine de service (ISR) :
Chaque composant périphérique peut générer soit une interruption IRQ ou une interruption FIQ. Des priorités
allant de 0 à 39 peuvent être attribuées à ces composants, indépendamment du type d’interruption (IRQ ou FIQ).
Ces composants ne possèdent toutefois pas des vecteurs d’interruption propres.
Une description détaillée du contrôleur d’interruption est fournie par [2], Intel PXA27x Processor Family
Developer's Manual. Ce chapitre fournit un bref aperçu du contrôleur d’interruption et une brève description des
registres les plus importants.
ICLR
ICMR
ICPR
ICIP
ICFP
ICHP
IPRx
Les registres de configuration et de statut du contrôleur d’interruption sont décrits dans le Tableau suivant. Les
noms des registres commencent par « IC », ce qui signifie « Interrupt Controller ».
Le Tableau suivant contient les différentes sources d’interruption et leur identité périphérique :
Interrupt
Disable Interrupts
1 spsr_mode = cpsr
pc = vector table entry
Save context
2
Interrupt Handler
3
Interrupt Service
4
Routine
Restore context
5
Enable Interrupts
6 pc = lr - 4
cpsr = spsr_mode
return
irq_handler:
SUB lr, lr, #4 @ adjust link register for return address
STMFD sp!, {r0-r3, r12, lr} @ save context
LDR r0, =ICIP @ load IRQ status register address
LDR r0, [r0] @ read IRQ status register
TST r0, #1<<PER_ID_OST @ test if operating system timer (OST) caused IRQ
BLNE timer_isr @ if yes, call timer ISR
TST r0, <other source> @ test other IRQ sources
...
LDMFD sp!, {r0-r3, r12, pc}^ @ restore context and return
8.1 Introduction
Le déroulement d’un programme peut être influencé à l’aide de structure de contrôle. Pour cela il existe les
procédures suivantes :
• Les séquences
• Les ramifications
• Les boucles
Une séquence est une suite d’instruction, qui est exécutée sans critères préalables. Alors que les ramifications et
les boucles ne sont exécutées qu’en fonction des critères externes, comme par exemple l’état du processeur.
Les langages de programmation évolués mettent à disposition des instructions standardisées pour définir des
structures de contrôle. Par exemple le langage de programmation C contient les instructions suivantes :
Dans le langage de programmation assembleur, il est également possible d’utiliser ces structures de contrôle. Ce
qui permet de mieux structurer le code et, par conséquent, de faciliter son entretient.
Contrairement aux langages de programmations évolués, l’assembleur ne met pas à disposition des instructions
pour la définition des structures de contrôle. Toutefois, ces structures peuvent être réalisée avec des séquences
d’instructions en utilisant des chablons. Les chapitres suivants montrent, comment ces structures de contrôles
peuvent être réalisées en assembleur.
if condition
true false
true- false-
block block
Lorsque la condition est réalisée, le programme exécute les instructions qui sont contenues dans le bloc
« then » ; sinon, il exécute les instructions du bloc « else ». Le bloc « else » est en option. C’est à dire
qu’une ramification simple ne peut contenir que du bloc « if ».
Exemple de code :
C Assembler
if if: TST <condition>
(condition)
{ BNE false
/* true-Block */ true: ... @ true-block
} B endif
else
{ false: ... @ false-block
/* false-Block */
} endif:
Une ramification simple sans le bloc « else » peur être programmée de la manière suivante :
C Assembler
if (condition) if: TST <condition>
{ BNE endif
/* true-Block */ true: ... @ true-block
}
endif:
Une ramification simple contient toujours une instruction d’entrée (ici « TST »), qui modifie les bits d’état le
registre CPSR (Current Program Status Register), suivie d’une instruction de saut de programme conditionnel
(ici « BNE »). Toutes les instructions, qui influencent les bits d’état, peuvent être utilisées comme instruction
d’entrée (TST, CMP, MOVS etc.) ; et toutes les instructions « Bcc » peuvent être utilisées comme sauts de
programme conditionnels.
Les architectures ARM soutiennent une autre forme de ramification simple : L’exécution conditionnelles des
instructions. Toutefois cela n’est raisonnable que pour des conditions simples, qui réunissent un à trois
instructions dans un bloc.
En assembleur ARM, il est possible de programmer cet algorithme avec des instructions conditionnelles. Ce qui
évite l’utilisation des sauts conditionnels ou inconditionnelles de la structure proposée au Tableau 45 (les
variables a, b et c sont stockées dans les registres r0, r1 et r2) :
CMP r0, r1 @ if (a > b)
MOVGT r2, r0 @ c = a
MOVLE r2, r1 @ c = b
switch expression
case const-expr1
default
block
Exemple de code :
C Assembler
switch (expression) switch: MOV r0, #expression
{
case (const-expr1): CMP r0, #const_expr1
... BEQ case1
break;
case (const-expr2): CMP r0, #const_expr2
... BEQ case2
break;
... ...
default: B default
...
break;
}
case1: ... @ const-expr1 block
B endSwitch
case2: ... @ const-expr2 block
B endSwitch
default: ... @ default block
endSwitch:
Au début de la ramification multiple, l’expression à évaluer est copiée dans un registre, afin de pouvoir être
comparée avec les constantes des étiquettes case. Lorsqu’il y a correspondance, le programme effectue un saut
à l’étiquette correspondant à la constante, pour y exécuter les instructions.
Le code peut devenir très lent, lorsque l’instruction switch contient de nombreuses étiquettes case. Dans ce
cas, il est recommandé d’utiliser des Tableaux de sauts (jump table). Ces derniers peuvent être programmés de la
manière suivante :
switch: ADR r1, jumptable @ load r1 with base address of jump table
MOV r0, #expression @ load r0 with expression to test
CMP r0, #tablemax @ check if value is in jump table
LDR LO pc, [r1, r0, LSL #2] @ ok Æ load pc with label form jump table
B default @ expression > tablemax Æ default
case1: ... @ const-expr1 block
B endSwitch
case2: ... @ const-expr2 block
B endSwitch
default:
... @ default block
endSwitch:
...
jumptable:
.word default @ case 0 not defined Æ default
.word case1
.word case2
Condition de la boucle
Corps de la
boucle
Exemple de code :
C Assembler
while while: TST <condition>
(condition)
{ BEQ endwhile
// Corps de la boucle … @ Corps de la boucle
} B while
endwhile:
Les bits d’état courant du programme sont modifiés en fonction de l’instruction d’entrée (comme ce fut le cas
avec la ramification simple). Lorsque la condition « n’est pas réalisée », le programme effectue ensuite un saut
de programme conditionnel à la fin de la boucle. Après exécution des instructions du corps de la boucle, le
programme fait un saut de programme non conditionnel au début de la boucle, afin de réitérer l’évaluation de la
condition.
Le code du Tableau 48 n’est pas très efficace, car il nécessite deux sauts de programme (B et BEQ), qui doivent
être exécutés à chaque passage. Ce code peut être optimisé avec une réorganisation. Dans ce cas, un seul saut de
programme conditionnel (BNE) ne doit être exécuté par passage.
while: B test
loop: … Schleifenkörper
test: TST <condition>
BNE loop
endwhile:
fois.
Corps de la
boucle
Condition de la boucle
Exemple de code :
C Assembler
do do:
{
// Corps de la boucle … @ Corps de la boucle
} while (condition); while: TST <condition>
BNE do
enddowhile:
Les bits d’état sont également modifiés dans cet exemple en fonction de l’instruction d’évaluation (ici « TST »).
Lorsque la condition est « vraie », le programme effectue un saut de programme conditionnel au début de la
boucle. La boucle do - while correspond ainsi à la version optimisée de la boucle while, introduite au
chapitre 8.4, sans la première instruction de branchement.
Corps de la
boucle
Exemple de code :
C Assembler
for (expr1; expr2; expr3) for: MOV r0, #valeur_début
{ loop: CMP r0, #valeur_fin
// Corps de la boucle BGE endfor
} ... @ Corps de la boucle
ADD r0, r0, #pas
bra loop
endfor:
Un registre rx (dans cet exemple r0) est utilisé comme variable compteur. Au début cette variable est initialisée
avec la valeur « valeur_début ». A l’étiquette « loop », le critère d’arrêt est évalué à l’aide de l’instruction
CMP, qui teste si la variable compteur a atteint la valeur « valeur_fin ». Si cette dernière atteint cette valeur
limite, le programme fait un saut de programme conditionnel (BGE) à la fin de la boucle. Si non, le programme
exécute les instructions du corps de la boucle et incrémente la variable compteur en fonction de la valeur
« pas ».
Des autres instructions que BGE peuvent également être utilisées pour évaluer la condition d’arrêt. L’instruction
ADD peut être remplacée par SUB afin de réaliser des pas de compteur décroissant.
9.1 Introduction
Les structures de données rassemblent plusieurs variables (de type simple ou composé) dans une structure. Voici
un exemple de structure de donnée en C :
struct Adresse
{
char Nom [MAX_CHAR];
char Prenom [MAX_CHAR];
char Route [MAX_CHAR];
int Numero;
char Lieu [MAX_CHAR];
};
Les langages de programmation évolués soutiennent les structures de données. Il en résulte les avantages
suivants :
• Compréhension et entretient facilité du programme
• Meilleur structure
En assembleur il est également possible de profiter des avantages des structures de données. Ce chapitre traite ce
sujet.
Les structures de données sont toujours orientées problème. C'est-à-dire, qu’ils essayent de reproduire au mieux
la réalité à l’aide d’un jeu de données (par exemple les données d’un processus). Il existe également une relation
étroite entre ces structures et le code du programme. Par conséquent, la modification d’une structure aura un
effet sur le code.
La directive « .space » permet de réserver un certain nombre de bytes, qui peuvent être initialisés. Dans cet
exemple 4 * 4 bytes ont été réservés au total et chaque byte a été initialisé avec la valeur 0x12. Si le Tableau ne
doit pas être initialisé, il peut également être défini dans la section « .bss ».
L’adresse de base du Tableau est d’abord copiée dans r0. Ce dernier adresse ainsi le premier élément du
Tableau (indexe 0). L’indexe de l’élément à accéder est ensuit copier dans r1. L’accès au Tableau est effectué
finalement avec l’instruction STR. L’adressage y est indirect et est obtenu avec « r0 + r1 * 4 ». La
multiplication avec un facteur scalaire (défini dans cet exemple avec LSL #2) est importante, car les adresses
sont toujours calculées en byte.
Important : Un facteur scalaire est utilisé pour l’adressage indirect. Ce dernier possède les valeurs suivantes :
• 1 pour les bytes
• 2 pour les demis mots
• 4 pour les mots
Le code ci-dessus doit être modifié de la manière suivant avec un Tableau, qui contient des éléments du type
byte au lieu du type mot :
.data
array .space 4, 0x34 @ array of 4 bytes, each initialized with 0x34
.text
LDR r0, =array @ load r0 with base address of array
LDR r1, #2 @ load r1 with index 2
MOV r2, #0 @ value to write is 0
STRB r2, [r0, r1] @ array[2] = 0 (Byte)
Les éléments de ce Tableau ne peuvent être stockés soit en ligne ou en colonne. Par exemple, le stockage en
ligne du Tableau, contenant 3 lignes et 4 colonnes, s’effectue de la manière suivante :
[0,0]
[0,1] 1. ligne
[0,2]
[0,3]
[1,0]
[1,1] 2. ligne
[1,2]
[1,3]
[2,0]
[2,1] 3. ligne
[2,2]
[2,3]
Adresse = 4 * i + j
Cette formule n’est valable que pour des Tableaux, qui possèdent une adresse de base nulle et 4 colonnes du type
byte. La formule générale, qui contient également l’adresse de base du Tableau et la taille des éléments, est la
suivante :
L’évaluation de cette formule peut s’avérer coûteuse, en fonction de la famille du processeur. Notamment
l’opération de multiplication « SZ * i » peut exiger un temps de calcul important. La multiplication avec le
facteur scalaire S peut, par contre, être effectuée avec des opérations de décalages (facteur 2 ou 4).
Remarque : Le caractère nul (′\0 ′) et celui du chiffre zéro (′0′) possèdent des valeurs ASCII différentes.
La structure « date » possède deux membres de type byte et un membre de type demi mot. L’accès aux
membres de la structure s’effectue à l’aide de déplacements (offset), qui définissent la position relative des
membres dans la structure. Ces derniers sont les suivants pour la structure « date » :
.set OFFSET_MONTH, 1
.set OFFSET_YEAR, 2
Le membre « day » ne nécessite pas d’offset, car son adresse est identique à celle de base de la structure date.
L’écriture du membre année dans le registre r0 s’effectue par exemple de la manière suivante :
pile. Cette dernière était destinée au transfert des paramètres, à la sauvegarde des registres et la définition des
variables locales. Mais, l’utilisateur peut également définir des autres piles pour la gestion de données.
Le pointeur de pile doit adresser le début de la pile. Du fait que la pile croît dans le sens négatif, ce début est
l’adresse la plus grande de la pile. Le pointeur de pile est par conséquent initialisé avec « stack +
STACK_SIZE ».
Pointeur Adresses
de pile hautes
Variable Adresses
pile basses
push:
STR lr, [sp, #-4]! @ save return address on system-stack
LDR r1, =stackpointer @ load r1 with stackpointer address
LDR r2, [r1] @ load r2 with stackpointer value
LDR r3, =stack @ load r3 with stack address
CMP r2, r3 @ check if stack is already full
BLE stack_full
SUB r2, #4 @ decrement stackpointer (decrement before)
STR r2, [r1] @ save new stackpointer value
STR r0, [r2] @ push value to stack
B endpush @ Æ end
stack_full: ... @ error code
endpush: LDR pc, [sp], #4 @ return
pop:
STR lr, [sp, #-4]! @ save return address on system-stack
LDR r1, =stackpointer @ load r1 with stackpointer address
LDR r2, [r1] @ load r2 with stackpointer value
LDR r3, =stack @ load r3 with stack address
ADD r3, #STACK_SIZE @ r3 holds stack top address
CMP r2, r3 @ check if stack is empty
BGE stack_empty
LDR r0, [r2] @ pop value from stack, r0 is return value
ADD r2, #4 @ increment stackpointer (increment after)
STR r2, [r1] @ save new stackpointer value
B end pop @ Æ end
stack_ empty: … @ error code
endpop: LDR pc, [sp], #4 @ return
Contrairement à la pile décrite au chapitre 6, qui ne contient pas de routine de contrôle pour les dépassements de
capacité, il est possible d’ajouter des fonctionnalités de contrôle, afin de pouvoir déceler les éventuels
dépassements.
Put
2. Element
Get
Dans la pratique, les queues de tampons sont remplacées par les anneaux de tampons. La problématique de
l’engloutissement de la mémoire est ainsi éliminée. Il existe deux types de réalisation :
a) Head et Tail peuvent adresser le même élément de mémoire. Dans ce cas une variable supplémentaire
« size » est également nécessaire, afin de pouvoir stocker le nombre d’éléments contenus dans l’anneau.
Ainsi, size = 0 correspond à un anneau vide.
size = 0 size = 1
n. Element
Figure 97: Les pointeurs Head et Tail dans une queue de tampons
b) Head et Tail ne peuvent pas adresser le même élément de mémoire. Un élément de mémoire est ainsi
sacrifié, mais la variable supplémentaire size n’est plus nécessaire (size = Tail – Head – 1). La
queue peut ainsi être initialisée avec « Head = 1 » et « Tail = 2 ».
Dans les deux cas il faut systématiquement contrôler les dépassements de capacités des pointeurs Head et
Tail. La Figure suivant illustre un exemple avec un dépassement de capacité de 8 à 1.
Dépassement
Head / Tail /
lecture écriture
1 8
2 7
3 6
4 5
Reset
Bootloader
Code de démarrage du système (CA RM E)
10.2 Le reset
Au début fut le reset. Ce dernier peut être généré par les événements suivants :
• Allumage
• Watchdog
• Touche reset
• Software
Le reset conduit le processeur dans un état défini. Dans la plupart des contrôleurs le reset est le premier vecteur
d’interruption (souvent à l’adresse 0). Ce dernier contient l’adresse de la première fonction, qui doit être appelée
au démarrage du système. Ce qui est normalement le code de démarrage du système (boot loader), lorsque ce
dernier est disponible, ou le code de démarrage de l’application.
3. Démarrage de l’application.
Remarque 1 : Le boot loader charge le système d’exploitation en premier, lorsque ce dernier est disponible. Les
applications sont chargées ensuite par le système d’exploitation.
Remarque 2 : Sur le kit de développement CARME, le boot loader contient un Tableau de vecteurs
d’interruption, qui se situe dans la mémoire flash à l’adresse 0. Les vecteurs de ce Tableau contiennent des
instructions de branchement sur tous les vecteurs d’interruption de l’application, qui se situent dans la mémoire
de travail à l’adresse 0xA0000000. La MMU ne sera pas utilisée pour les projets C et assembleur.
La programmation du processus de démarrage n’est pas requise avec les programmes en C. En effet, le
compilateur lit automatiquement le code de démarrage à partir d’une librairie et l’insère dans le projet.
L’application C démarre alors avec la fonction principale « main ». Cependant il existe de nombreux cas, où le
code de démarrage doit être modifié. Ce qui exige des connaissances en assembleur.
Le code de démarrage des projets CARME C se trouve dans le fichier « crt0.s », qui se trouve dans le sous
directoire « startup ».
.global _startup
.section .startup
Reset_Handler:
/* Startup-Code der Applikation */
…
Les registres les plus importants du système sont initialisés par le boot loader. Des registres supplémentaires
peuvent être initialisés à cet endroit, comme par exemple la vitesse de la CPU etc.
Initialisation du la pile
Le code de démarrage de l’application doit initialiser les piles (stack) pour les différents modes (Superviseur,
IRQ, FIQ etc.). En particulier, les pointeurs de piles doivent adresser les espaces mémoire, qui ont été réservés
pour ces piles.
La taille de chaque pile est définie dans le fichier crt0.s de l’environnement de développement CARME. Par
exemple pour le mode FIQ :
.global FIQ_STACK_SIZE
.set FIQ_STACK_SIZE, 0x00000200 @ stack size for FIQ mode
Les zones de mémoire des différentes piles sont définies dans le fichier « ldscript_ram », dont voici un
aperçu :
...
. = ALIGN(4);
_irq_stack_top_address = .; @ IRQ-Stack top address
. += FIQ_STACK_SIZE; @ reserver FIQ stack size
. = ALIGN(4);
_fiq_stack_top_address = .; @ FIQ-Stack top address
...
Les pointeurs de piles sont initialisés dans le fichier crt0.s. Par exemple pour la pile FIQ, cela est définie de la
manière suivante :
LDR r0, =_fiq_stack_top_address @ load FIQ stack top address
MSR CPSR_c, #MODE_FIQ|I_BIT|F_BIT @ switch to FIQ mode, interrupts disabled
MOV sp, r0 @ load stack-pointer with stack address
Es ce que vous vous être déjà poser la question suivante : comment cette variable est elle initialisée ? Cette
instruction n’est exécutée par aucune fonction durant tout le déroulement du programme. Cette initialisation est
par conséquent une tâche supplémentaire du code de démarrage, qui est réalisée par un processus appelé « le
traitement ROM » (ROM-Processing).
RAM ROM/Flash
_data
.data
/* variable globale
à initialiser */
static int myInt = 5;
_edata
_etext
Valeur d’initialisation
Le code de démarrage copie les valeurs d’initialisation des variables globales, depuis le flash dans la RAM. Cela
peut être programmé de la manière suivante :
/* copy .data section, ininialized variables */
LDR r1, =_etext
LDR r2, =_data
LDR r3, =_edata
cpydata_loop:
CMP r2, r3
LDR LO r0, [r1], #4
STRLO r0, [r2], #4
BLO cpydata_loop
_etext est l’adresse de départ de la zone de la flash, qui contient les valeurs d’initialisation. _data et
_edata désignent respectivement le début et la fin de la zone .data, qui contient les variables globales à
initialiser. _etext, _data et _edata sont définies dans le fichier ldscript_ram.
Remarque : Lorsque vous travaillez avec le débuggeur, vous chargez le programme dans la RAM, à travers son
interface. Dans ce cas, le débuggeur initialise automatiquement les variables globales. Les systèmes, prêts pour
la commercialisation, ne contiennent plus de débuggeur pour réaliser cette initialisation. Dans ce cas, il faut
absolument utiliser le code de démarrage.
L’initialisation du hardware peut être critique en temps lorsque, par exemple, les sorties du processeur doivent
être rapidement fixés à des niveaux prédéfinis (Exemple : contrôle de moteur). Cette initialisation doit
éventuellement être réalisée dans le code de démarrage ou de façon hardware.
Dans uns deuxième temps les fonctions des applications sont appelées de façon cyclique, à l’aide d’une boucle
infinie.
=
Initialisation du hardware
Boucle infinie
...
Exemple :
Lorsque des bytes sont reçus à l’aide d’une interface sérielle, qui génère des interruptions, il faudrait uniquement
stocker les bytes dans un tampon. Le traitement de ces bytes devrait être réalisé en dehors de la routine de
service.
Un point très important est l’échange de données entre les routines de services et les fonctions de l’application.
Ici la communication n’est possible qu’à l’aide de variables globales (a moins que le système possède un RTOS).
Mais il faut toujours faire attention au fait que les routines de service peuvent sur écrire les données, sans que les
fonctions de l’application principale ne le remarque. Cela est surtout critique avec les données structurées. Par
exemple supposons que le programme possède un tampon de réception de 10 bytes, qui sont remplis par une
routine de service et qui sont lus par les fonctions de l’application principale. Admettons que durant la lecture du
cinquième byte, par une des fonctions de l’application principale, une interruption apparaît pour indiquer la
réception de 10 nouveaux bytes. La routine de service sur écrit alors les 10 bytes du tampon de réception. La
fonction de l’application principale va continuer sa lecture avec le sixième byte, comme si rien ne s’était produit.
Finalement l’application va poursuivre son travaille avec des données inconsistantes. Il faut donc introduire des
mécanismes, qui permettent der verrouiller ces opérations mutuellement. C'est-à-dire qu’aucune interruption de
réception ne devrait être générée pendant que les données sont ne train d’être lues. Le tout peut devenir très
complexe, lorsque plusieurs applications sont exécutées simultanément. Si cette complexité n’est pas maîtrisée
(cela est sûrement le cas, lorsque des nouvelles fonctionnalités, qui n’étaient pas planifiées ont été ajoutée par la
suite), le système va chuter immanquablement.
1er fonction de
l’application
2ième fonction
nième
fonction de l’application
de l’application
4ième fonction
de l’application
Variables
globales
Figure 102 : Transfert de données entre les routines de service et les fonctions de l’application
Un RTOS n’est pas nécessaire pour les applications simples. Par contre, un RTOS est recommandé pour les
applications complexes, si les ressources hardware le permettent (CPU, taille de la mémoire etc.). Le module
d’approfondissement « système d’exploitation temps réel » traite ce sujet plus en détail.
11 L’interface C et assembleur
11.1 Introduction
Aujourd’hui la plupart des microcontrôleurs sont programmés à l’aide de langages de programmation évolués,
comme le C et le C++. Toutefois, les parties du code, qui sont critiques soit en temps ou en mémoire, sont
toujours définies en assembleur. Ce qui est particulièrement le cas pour les fonctionnalités suivantes :
• Accès optimal au hardware, spécialement pour les driver
• Les algorithmes, qui exigent une puissance de calcul élevée, afin de pouvoir utiliser la CPU de façon optimal
(unité de multiplication et d’accumulation)
• Les fonctions qui sont souvent traitées, afin d’augmenter la performance du système
• Accès aux registres de la CPU, qui ne sont pas accessibles en C
Dans ce cas une attention particulière doit être apportée sur l’interface C/assembleur. Cette interface dépend
technologiquement de la famille des microcontrôleurs et de leur environnement de développement. Les points
suivants sont à considérer :
• Appel des sous routine assembleur depuis les fonctions C et inversement
• Transmission de paramètres et retour de valeur
• Utilisation de variable C en assembleur et inversement
• Assembleur en ligne
Les mécanismes décrits dans se chapitres se réfèrent au PXA27x de XScale, au compilateur GNU (gcc) et à
l’assembleur (gas) de cette famille de processeur.
void main(void)
{
...
res = asmsub(par1, par2);
...
}
L’exemple suivant contient une variable globale définie en C (c_var) et une variable globale définie en
assembleur (asm_var) ainsi que les accès sur ces dernières :
Code C :
/* global variables */
int c_var; // reserve memory for word-variable
extern int asm_var; // specified in assembler source
void ctest_var(void){
...
asm_var = 0x22; // modify assembler variable
c_var = 0x55; // modify C variable
}
Code assembleur :
.data
asm_var: .space 4, 0x11 @ global asm_var, initialized with 0x11111111
.text
asmtest_var:
STR lr, [sp, #-4] @ save return address on stack
LDR r0, =c_var @ r0 holds address of variable c_var
MOV r1, #0x55 @ r1 holds value to write to c_var
STR r1, [r0] @ c_var = 0x55
LDR r0, =asm_var @ r0 holds address of variable asm_var
MOV r1, #0x11 @ r1 holds value to write to asm_var
STR r1, [r0] @ asm_var = 0x11
LDR pc, [sp], #4 @ return
Exemple :
asm volatile ("AND %0, %1, #0xFF" : "=r" (result) : "r" (value));
Cette instruction peut paraître un peu compliquée. « volatile » est obligatoire, afin que le compilateur C
n’optimise pas l’instruction asm. L’instruction en soit est composée de quatre colonnes :
1. Le code contient les instructions assembleur, qui ont été définies sous forme de chaîne de caractère :
"AND %0, %1, #0xFF"
2. Output operand list : Les opérandes de sortie, séparées par des virgules, peuvent être définies dans la liste
des opérandes de sortie : "=r" (result). Cette liste des opérandes permet d’accéder également aux
variables C. %0 se réfère au premier élément de la liste. "=r" Signifie que le compilateur peut utiliser un
registre, mais uniquement en mode écriture « write only ».
3. Input operand list : Les opérandes d’entrée, séparées par des virgules, peuvent être définies dans la liste
des opérandes d’entrée : "r" (value). Du fait que ce dernier est le deuxième opérande, %1 permet
d’accéder à la variable value. "r" signifie que le compilateur doit utiliser encore un registre
quelconque.
4. Clobber list : Cette liste informe le compilateur sur les registres, mémoire ou de statut, qui doivent être
modifiés par le code assembleur. La liste clobber est vide dans l’exemple de ci-dessus.
L’exemple le plus simple : Une seule ligne d’assembleur est programmé, sans opérande et sans liste clobber :
asm volatile ("MOV r0, r0"); // NOP (No Operation)
asm volatile (
"MOV r0, r0" "\n\t"
"MOV r0, r0"
);
"\n\t" permet d’augmenter la lisibilité du code assembleur, qui est généré par le compilateur.
Si une variable C doit être utilisée en tant que paramètre d’entrée, elle peut être définie dans une liste des
opérandes d’entrée. L’exemple suivant ne contient pas de paramètres de sortie, la liste des opérandes de sortie est
ainsi vide. L’exemple illustre également l’accès au registre CPSR, ce qui ne serait pas possible en C :
asm volatile("MSR CPSR, %0" : : "r" (status)); // load CPSR register with value in status
Le compilateur génère à partir de cette instruction ASM le code objet (désassemblé) suivant :
LDR r3, =status
LDR r3, [r3]
MSR CPSR, r3
La liste des opérandes d’entrée spécifie l’état "r" pour la variable status. Le compilateur peut ainsi choisir le
un registre quelconque pour exécuter l’instruction. Le registre r3 a été choisi dans l’exemple de ci-dessus.
Il faut informer le compilateur, lorsque l’instruction assembleur change le contenu d’un registre. Le compilateur
ne possède pas les moyens pour remarquer cela. Il dépend donc de cette information, afin qu’il n’y ait pas de
problèmes avec ses propres registres. La déclaration des registres, qui sont modifiés par l’instruction assembleur,
s’effectue dans la liste clobber :
asm volatile("MOV r1, r0" : : : "r1"); // register r1 modified
Ce code possède un paramètre d’entrée, qui est la variable value. value est combiné de façon AND avec la
valeur #0xFF, c'est-à-dire que les 3 bytes de poids plus fort sont effacés. Le résultat de l’opération est déposé
dans la variable result. L’exemple illustre comment l’instruction assembleur peut être étendue et complétée
avec des variables C. Cela n’est par conséquent plus une instruction assembleur pure, mais plutôt un mélange de
C et d’assembleur. Le compilateur génère à partir de cette instruction ASM le code objet (désassemblé) suivant :
LDR r3, =value
LDR r3, [r3]
AND r2, r3, #255
LDR r3, =result
STR r2, [r3]
Du fait que ce code a été généré par le compilateur, il ne faut pas déclarer les registre r2 et r3, qui ont été
modifiées, dans la liste clobber.
Exemple : Il faut définir une variable « led », qui permet d’accéder à la ligne de LED sut le kit de
développement CARME. Ces LED sont accessible à l’adresse 0x0C003000.
L’exemple ci-dessus du manuscrit du relieur montre, comment les registres de configuration des différentes
entrées et sorties du kit de développement CARNEIO1 peuvent être rassemblés en sections.
Exemple :
volatile unsigned short reg1 _at 0x1000;
Le bit le plus faible d’un nombre binaire (byte, demi mot et mot) est qualifié de LSB (Least Significant Bit). Le
bit le plus fort est le MSB (Most Significant Bit).
Un demi mot est composé de deux bytes : le « lower Byte » (les bits 0 à 7) et le « upper Byte » (les bits 8 à 15).
Un mot est composé de deux demi mots : « lower Halfword » (bit 0 bis 15) et le « upper Halfword » (bit 16 bis
31).
Complément à deux
Le complément à deux permet de représenter les nombres négatifs dans le système binaire. Des symbole
supplémentaire comme + et – ne sont ainsi plus nécessaire.
Dans le complément à deux, les nombres positifs sont représentés avec un premier bit 0 (le bit de poids le plus
fort qui est également appelé le bit de signe). Les valeurs négatives sont représentées avec un premier bit 1. Ces
dernières sont codées de la manières suivante : tous les bits du nombre positif sont d’abord inversés et la valeur 1
est ensuite additionné au résultat.
Exemple :
+4(10) = 00000100
-4(10) = 11111011 + 1 = 11111100
-1(10) = 11111111
127(10) = 01111111
-128(10) = 10000000
Le complément est un système de nombres signés, qui ne possède qu’une seule représentation de la valeur nulle.
Les additions et les soustractions peuvent être ainsi être effectuées sans dissociation de cas. Il n’existe qu’une
seule représentation de la valeur nulle, si l’on admet que les bits, résultants des dépassements de capacité, sont
tronqués durant les opérations.
Il existe plusieurs possibilités pour stocker la mantisse et l’exposant dans la mémoire. Par exemple avec la
précision simple (32 bits) ou double (64 bits). Ce chapitre se concentre uniquement sur le format précision
simple. Ce dernier est stocké dans la mémoire de la manière suivante :
31 30 23 22 0
V E + 127 M
1 bit 8 bits 23 bits
Seule la partie fractionnaire de la mantisse est stockée dans la mémoire (hidden bit). Il en résulte la formule
suivante pour la construction des nombres à virgule flottante :
Z = (-1)V M * 2E-127
Annexe C: La documentation
Nom Edition / Auteur Référence
ARM Architecture Reference Manual, ARM Limited [1]
ARM DDI 0100E
Intel PXA27x Processor Family Developer's Manual Intel [2]
Description détaillée des composants périphériques du chip, 1246 pages
Intel PXA27x Processor Core Developer's Manual Intel [3]
Registres du coprocesseur, cache des instructions et des données, les
spécialités du processeur, 220 pages
Intel PXA270 Electrical, Mechanical and Thermal Specification Intel [4]
Timings, pin out … 128 pages
Intel PXA27x Processor Family Memory Subsystem Intel [5]
Flash, LPSDRAM, 138 pages
Intel XScale Microarchitecture Intel [6]
Technical Summary, 14 pages
Intel PXA27x Processor Family Design Guide Intel [7]
Intel PXA27x Processor Developer's Kit Intel [8]
Tableau 52 : Documentation
Index
boucle d’itération............... 129
. 8
boucle do while ............ 134
.2byte ............................... 93 8051......................................28 boucle for ........................ 134
.4byte ............................... 93 boucle while ................... 133
A
.align ............................... 90 Branch Target Buffer........... 34
.arm .................................... 89 abt.........................................39 break point........................... 87
.ascii ............................... 92 Acknowledge bit ..................24 BSP.................................... 146
.asciz ............................... 92 ADC ................................78, 79 bus d’adresse ................... 6, 46
.asm .................................... 57 ADD ................................78, 79 bus de contrôle....................... 6
.balign ............................. 91 ADR ....................................102 bus de données................. 6, 46
adressage des registres .......153 BX ........................................ 84
.bin .................................... 58
adresse de retour.................106 byte .............................. 43, 155
.bss .................................... 95
adresse physique...................14
.byte ................................. 93 C
adresse virtuelle....................14
.data ................................. 95 call by reference ................ 110
ALU .................................5, 36
.elf .................................... 58 amplificateur de sortie ..........18 call by value....................... 110
.else ............................... 100 AND ................................78, 81 callee.................................. 105
.end .................................... 99 anneau de tampons .............140 caller .................................. 105
.endif ............................. 100 APCS..........107, 110, 112, 150 caractérisiques AC............... 12
.endm ............................... 101 architecture.............................3 caractérisiques DC............... 12
.equ .................................... 95 architecture ARM.................30 carry..................................... 42
.extern ............................. 90 architecture de Harvard ..........4 case mémoire ....................... 10
.global ............................. 90 architecture de stockage .......11 cercle de nombre.................. 42
.hword ............................... 93 architecture de von Neumann.3 chaîne de caractère ............ 138
.if .................................... 100 architecture RISC .................30 champ des étiquettes............ 51
.ifndef ........................... 101 argument.............................113 chip select .............................. 8
arithmétique des entiers........78 choix parmi une liste............ 62
.include .......................... 94
ARM.....................................30 CISC .................................... 27
.irp .................................. 104
ARMV5TE...........................60 clobber list ......................... 151
.lis .................................... 57 CLZ ...................................... 87
array ...................................136
.macro ............................. 101 ascending............................107 CMN ................................ 78, 79
.map .................................... 58 asm ....................................151 CMP ................................ 78, 79
.o......................................... 57 ASR ......................................67 code d’opération .................. 62
.org .................................... 95 assemblage conditionnel.....100 code de condition................. 63
.p2align .......................... 92 assembleur............................57 code de démarrage............. 144
.rept ............................... 103 assembleur en ligne ............151 code machine ................. 49, 57
.s......................................... 57 assignation des pins..............12 code récursif ...................... 106
.section ............ 95, 96, 153 coeur .................................... 38
B cœur d’exécution ................. 35
.section absolue .................. 153
.set .................................... 95 B 84 commentaire ........................ 53
.space ............................... 94 Banked Register ...................41 compilateur .......................... 57
.text ................................. 95 Barrel Schifter ................36, 67 complément à deux.............. 55
.thumb ............................... 89 baud rate ...............................20 complément à un.................. 55
.word ................................. 93 BIC ................................78, 81 compteur de program........... 41
big endian .............................43 compteur de programme 5, 113
_ binaire...................................55 condition.............................. 81
bit .......................................155 condition d’arrêt .................. 24
__attribute__ ............ 153
bit C......................................42 condition de démarrage ....... 24
_at .................................... 154
bit d’état ...............................41 Condition Flags ................... 41
_at_ .................................. 154 constante........................ 53, 63
bit F ....................................119
= bit I .....................................119 contexte ............................. 128
bit N......................................42 Contitional Execution .......... 42
= 95 bit V......................................42 control bus ............................. 6
1 bit Z ......................................42 contrôleur d’interruption...... 38
BKPT ....................................87 contrôleur d’interrutpion.... 124
1 of X Decoder....................... 8 BL .................................84, 106 convertisseur A/D................ 26
6 BLX ......................................84 convertisseur D/A................ 26
bootloader...........................143 coprocesseur ........................ 65
68HCxx................................ 28 boucle .................................129 Coprocessor ......................... 35