Processeur Elementaire ARM Cortex


1 - Programme exemple : somme des éléments d’un tableau

Description du Programme :

Nous allons illustrer le fonctionnement d’un processeur élémentaire avec un programme simple mettant en jeu une boucle de calcul, à savoir la somme de 5 éléments présents dans un tableau.
Le résultat du calcul doit être placé en mémoire dans la variable ‘résultat’.

En langage C

int tab[5]={1,2,3,4,5};
int resultat = 0;

void main() 
{
	int compteur=5;
	int acc=0;
	int i=0;
	
	while( compteur > 0 )
	{
		acc = acc + tab[i];
		i=i+1;
		compteur = compteur - 1;
	}
	
	resultat = acc;
	
}

Organigramme

organigramme.svg

Représentation des données en mémoire

La mémoire est indexée par octet ( si j’augmente l’adresse de 1, je passe à la case suivante, chaque case contenant 8 bits).
Dans une architecture 32 bits, un entier int est codé sur 32 bits ( 4 cases de 8 bits ).

donnees_memoire.svg


2 - Architecture

  • ALU : Unité Arithmétique et Logique pour effectuer les calculs
  • R0, R1, R2, R3 : Registres généraux, pour le calcul / contenant des données récupérées ou à écrire en mémoire / contenant une adresse afin de pointer une case en mémoire.
  • PC : Program Counter : contient l’adresse de l’instruction à exécuter
  • IR : Instruction Register : contient le code de l’instruction à exécuter ( que l’on va chercher en mémoire ).
  • SR : Status Register : contient les bits d’état NZVC suite à l’exécution d’un calcul ( est ce que mon résultat est négatif / nul / débordement signé ou non signé.
  • FSM : Finite State Machine : Le chef d’orchestre, après consultation du registre IR, contrôle les registres et l’ALU pour exécuter l’instruction.
  • Mémoire RAM : contient les instructions à exécuter ( mémoire programme ), et les données ( mémoire données )

mu0_risc_architecture.svg

Fonctionnement de la Machine d’Etats

Le Cycle Fetch/Decode/Execute simplifié :

machine_etat.svg

FETCH ( Chargement ) :
On prend l’adresse présente dans PC, et on va chercher dans la mémoire programme le code de l’instruction à exécuter, que l’on enregistre dans le registre IR.
A l’issue du FETCH, on incrémente la valeur de PC afin de pointer sur l’instruction suivante.

DECODE AND EXECUTE :

En consultant le registre IR, la machine d’état connait l’opération à exécuter.
Elle met alors tout en oeuvre pour contrôler l’UAL et les registres R1,R2,R3 et R4 afin d’exécuter l’instruction.


3 - Jeu assembleur

Codage d’une Instruction :

Toutes les instructions respectent la syntaxe suivante :

Opcode REG_DEST, REG_OP1, REG_OP2, #imm : REG_DEST <– opcode(REG_OP1, REG_OP2+#imm)

Chaque instruction est codée sur 32 bits, selon le format suivant :

codage_instruction.svg

J’ai donc 32 bits pour :

  • dire quelle opération je souhaite exécuter ( addition, saut, .. )
  • indiquer les éventuels registres destination et source
  • ajouter un offset au registre source 2 si nécessaire ( cette valeur immédiate est précédée de #)

Instructions arithmétiques et logiques__

MOV R1, R2 // R1<–R2

mov.svg

MOV R2,#5 // R2 <– 5

ADD R3,R2,R1 // R3 <– R2+R1
ADD R1,R2,#1 // R1 <– R2+1

add.svg

SUB R3,R2,R1 // R3 <– R2-R1

Instruction de lecture/écriture de données en mémoire

Dans une architecture RISC, seules les instruction de type LOAD et STORE permettent d’accéder à la mémoire.

LDR : permet de charger dans un registre une donnée se trouvant en mémoire de données.
Il faut donc spécifier le registre destination recevant la donnée, et l’adresse de la donnée.

LDR R1,[R0] : je vais chercher en mémoire la donnée dont l’adresse se trouve dans R0, je place la valeur de cette donnée dans R1.

ldr1.svg

Les instructions sont codées sur 32 bits Je ne peux donc pas indiquer dans une même instruction le nom de l’instruction, un numéro de registre et une __adresse mémoire de 32 bit__s.
Pour placer dans R0 l’adresse d’une variable, j’utilise l’instruction suivante :

LDR R0,[PC+offset] : R1 reçoit une valeur correspondant à une adresse d’une variable en mémoire ; le compilateur a placé lors de la compilation cette valeur d’adresse à l’adresse PC+offset.

STR : permet d’écrire en mémoire le contenu d’un registre, en spécifiant dans un autre registre la case mémoire souhaitée.

Ex: STR R1,[R0] : je vais écrire dans la case mémoire pointée par R0 ce qui se trouve dans le registre R1.

Il faut bien entendu au préalable placer dans R0 l’adresse qui va bien avec une instruction LDR.

str1.svg

Instructions de Saut

Une condition if then else ou une boucle for/while en C se traduit par un saut conditionnel en assembleur.
La condition se trouve dans le registre SR.

BNE : Branch if not Equal to Zero : si le résultat de mon opération précédente ( par exemple une soustraction ) est non nul ( autrement dit mon bit d’état Z=0 ), alors j’effectue un saut.
Cela consiste à forcer la valeur de PC avec une adresse indiquée avec l’instruction BNE. Si ma condition n’est pas respectée, autrement dit j’ai obtenu un résultat nul ( Z=1 ) précédemment, dans ce cas je ne fais pas le saut et passe donc à l’instruction suivant bne.

bne.svg


4 - Compilation et Exécution du programme de Calcul

Programme Assembleur

R1 = compteur
R2 = i
R3 = acc

        MOV R3,#0	   // R3 <- 0  
        LDR R0,[PC+56] // R0 <- addr compteur  
        LDR R1,[R0]    // R1 <- val compteur  
        LDR R2,[PC+52] // R2 <- addr tab  
LOOP:   LDR R0,[R2]    // val <- tab[R2]          
		ADD R3,R3,R0   // acc <- acc+val  
        ADD R2,R2,#4   // R2++   
        SUB R1,R1,#1   // compteur --  
        BNE LOOP       // PC <- PC - 12  
        LDR R0,[PC+32] // R0 <- addr resultat  
        STR R3,[R0]    // resultat <- R1    
        STOP  

REMARQUE : R2 pointe sur les éléments du tableau. Comme chaque valeur fait 32 bits, soit 4 cases de 8 bits, il faut bien incrémenter R2 de 4 à chaque tour de boucle.

Contenu de la Mémoire ( disassembly )

--################  PROGRAM MEMORY  #########################
--|  index  | OP1 OP2  | 0000 DEST |   INSTR  |   adresse  
  "00000000","11111111","00000011","00000010", --00 01 02 03-      MOV R3,#0	  // R2 <- 0
  "00111000","11110100","00000000","00000000", --04 05 06 07-      LDR R0,[PC+56] // R0 <- addr compteur
  "00000000","11110000","00000001","00000000", --08 09 10 11-      LDR R1,[R0]    // R1 <- val compteur   --
  "00110100","11110100","00000010","00000000", --12 13 14 15-      LDR R2,[PC+52] // R2 <- addr tab
  "00000000","11110010","00000000","00000000", --16 17 18 19-LOOP: LDR R0,[R2]    // val <- tab[R2]
  "00000000","00110000","00000011","00000011", --20 21 22 23-      ADD R3,R3,R0   // acc <- acc+val
  "00000100","00101111","00000010","00000011", --24 25 26 27-      ADD R2,R2,#4   // R2++ 
  "00000001","00011111","00000001","00000100", --28 29 30 31-      SUB R1,R1,#1   // compteur --
  "11101100","11110100","00000100","00001010", --32 33 34 35-      BNE LOOP       // PC <- PC - 12
  "00100000","11110100","00000000","00000000", --36 37 38 39-      LDR R0,[PC+32] // R0 <- addr resultat
  "00000000","11110000","00000011","00000001", --40 41 42 43-      STR R3,[R0]    // resultat <- R1  
  "00000000","00000000","00000000","00001111", --44 45 46 47-      STOP
  "00000000","00000000","00000000","00000000", --48 49 50 51-
  "00000000","00000000","00000000","00000000", --52 53 54 55-
  "00000000","00000000","00000000","00000000", --56 57 58 59-  
  "00000000","00000000","00000000","00000000", --60 61 62 63- 
  "01001100","00000000","00000000","00000000", --64 65 66 67- adresse compteur ( 76 )     
  "01010000","00000000","00000000","00000000", --68 69 70 71- adresse tab  	   ( 80 )
  "01100100","00000000","00000000","00000000", --72 73 74 75- adresse resultat ( 100 )   
--################  DATA MEMORY  #########################  
  "00000101","00000000","00000000","00000000", --76 77 78 79- compteur ( data=5 )
  "00000001","00000000","00000000","00000000", --80 81 82 83- tab	   ( data=1 )  
  "00000010","00000000","00000000","00000000", --84 85 86 87-    	   ( data=2 ) 
  "00000011","00000000","00000000","00000000", --88 89 90 91-    	   ( data=3 ) 
  "00000100","00000000","00000000","00000000", --92 93 94 95-    	   ( data=4 ) 
  "00000101","00000000","00000000","00000000", --96 97 98 99-    	   ( data=5 )  
  "00000000","00000000","00000000","00000000", --100 101 102 103- resultat ( data=0 )
  "00000000","00000000","00000000","00000000", --104 105 106 107- 
  "00000000","00000000","00000000","00000000", --108 109 110 111-
  "00000000","00000000","00000000","00000000", --112 113 114 115- 
  "00000000","00000000","00000000","00000000", --116 117 118 119-
  "00000000","00000000","00000000","00000000", --120 121 122 123-
  "00000000","00000000","00000000","00000000"  --124 125 126 127-  

Exécution

La description VHDL du processeur mu0-risc est disponible ici :

mu0_risc.zip