COMPTOIR
  
register

Hard du hard • Ça fonctionne comment, un processeur ? (Partie 1)

• Anatomie externe d'un CPU 

De koi k'on parle au juste ?

Le sigle CPU signifie "Central Processing Unit", ou "Unité de traitement centrale" pour les anglophobes. Il en a existé de nombreux types différents, dont les plus connus du grand public sont aujourd'hui les ARM, utilisés dans l'informatique mobile et les téléphones, et les x86 pour PC de bureau aussi bien que serveurs. Son rôle général est, effectivement, d'exécuter un programme, par exemple le démarrage d'un OS, et cela le plus rapidement possible.

 

Si "processeur" est un terme générique concernant une puce effectuant des calculs (le terme se cache dans GPU par exemple), on parlera par abus de langage de processeur pour désigner le CPU.

 

Pour cela, le processeur manipules des données, qu'il charge depuis la RAM (ou initialise directement à une valeur donnée) dans un registre. Un registre est donc un espace mémoire directement accessible par le processeur. Par exemple, les processeurs x86 actuels possèdent 16 registres depuis le passage au 64 bits (bien que les deux événements n'avaient pas de raison d'être directement reliés). Il existe en outre des registres spéciaux, par exemple le PC pour Program Counter ou Compteur Ordinal correspondant à l'adresse de l'instruction prochainement exécutée ; ainsi qu'un registre de condition gardant le résultat d'une opération précédente, utilisé lors de sauts conditionnels.

 

Comment qu'on lui parle ?

Le langage - si on peut l'appeler ainsi - utilisé pour transmettre des instructions au processeur est l'assembleur. On classe ainsi les processeurs par leur jeu d'instruction, ou ISA (Instruction Set Architecture). Les jeux d'instructions les plus connus sont aujourd'hui l'AMD64, aussi connu sous le nom de x86_64, issue de l'évolution des vieux Intel 8086 (d'où le nom x86) ; et l'ARM se déclinant sous différentes versions, ce dernier ayant par le passé cassé la rétro-compatibilité (par exemple, un processeur ARMv8-A ne peut pas exécuter un code en ARMv6). Mais des alternatives existent, comme les Intel Itanium (l'implémentation des bleues du 64 bits, cassant la compatibilité x86, qui a échoué en partie car Microsoft décida de ne supporter que l'AMD64 rouges) ou encore le RISC-V, une ISA libre de droit.

Une instruction assembleur est un bloc atomique de programme compris par le CPU. Elle correspond directement à un chiffre binaire indiquant l'état de certains pins du processeur. Bien qu'on l'écrive avec des mnémoniques, un langage plus compréhensible, par exemple "ADD R1, R1, #1" ; il ne s'agit que d'une traduction mot à mot de l'instruction, par exemple 0101000101011111. On  la représente d'ailleurs le plus souvent en hexadécimal (4 chiffres binaires = un chiffre-lettre de 0 à F), ici 515F.

 

Le binaire correspond à une représentation de nombres avec des 0 et des 1 uniquement. On effectues les calculs avec des retenus comme dans le système standard : on compte alors comme ceci : 1, 10, 11, 100, 101, 110, 111, etc. Cela est particulièrement utile ici car le "fils" utilisés n'ont que deux états : du courant passe (1) ou non (0).

 

hex to bin to dec

Convertion Hexadécimale -> Binaire -> Décimal

 

Dans une instruction, les premiers bits (leur nombre dépendant de l'ISA) s'appellent opcode, et désignent le type d'opération à effectuer : chargement dans un registre, rangement en mémoire, opération arithmétique, logique, calcul, sauts (aussi appelés branchements). Les autres bits indiquent les opérandes, qui peuvent être soit des registres soit des constantes appelées immédiats (décalage par rapport à une addresse, valeur constante pour un calcul, etc). Un immédiat est dit signé s'il peut être positif ou négatif, sinon il est non signé (unsigned).

 

Les entiers signés peuvent être codés de deux manières différents : soit un bit est reservé au signe (et on a alors deux codes pour zéro : "+0" et "-0") ; soit on utilise le complément à deux :  par exemple, on code "-42" par "nombre maximal (tous les bits à 1) - 42 + 1".

 

Lorsque l'on parle de processeur 64 bits, c'est ici que s'effectuent les changements : les instructions sont capables de gérer des adresses mémoires de 64 bits (wow, surprise !), ainsi que la gestion des calculs arithmétiques et logiques selon des représentations sur 64 bits.

 

Notez qu'aujourd'hui, l'adressage mémoire ne se fait que sur 48 bits, c'est pourquoi seul un maximum de 256 To de RAM est possible.

 

Originellement, on différenciait le RISC (Reduced Instruction Set Computer) du CISC (Complex Instruction Set Computer), le premier prévilégiant un jeu d'instructions à peu d'opérandes, mais plus rapides, et le second à de nombreuses opérandes plus spécialisées. Si l'x86 est de type CISC à la base (bien qu'en interne, le fonctionnement ressemble plus à un RISC émulant du CISC par soucis de rétro-compatibilité), l'ARM est de type RISC, autant dire qu'il n'existe pas de meilleur choix dans l'absolu.

 

NomOpcodeNom, mais en plus clairComment qu'ça s'utilise ?Et ça fait quoi ?
ADD 000 Addition add rA, rB, rC rA = rB + rC
ADDI (bouh) 001 Addition d'un immédiat (entier) add rA, rB, Imm rA = rB + Imm
NAND 010 Opération NAND : NON-ET add rA, rB, rC Le x-ème bit de rA vaut 0 si les x-ème bits de rB et rC valent tout deux 1. Sinon, le x-ème bit de rA vaut 1.
LUI 011 Chargement d'un entier (Load Upper Immediate) lui rA, Imm Les 10 bits premièers bits de rA prennent la valeur de Imm
SW 100 Stockage en mémoire (Store Word) sw  rA, rB, Imm Ecrit la valeur de rA en RAM à l'adresse rB + Imm
LW 101 Charger depuis la mémoire (Load Word) lw rA, rB, Imm Ecrit dans rA la valeur en RAM située à l'adresse rB + Imm
BEQ 110 Branchement si égal (Branch if EQual) beq rA, rB, Imm Si rA = rB, allors saute Imm instructions. Sinon continue l'exécution.
JALR 111 Saut et chargement de registre (Jump And Load Register) jalr rA, rB Saute à l'adresse contenue dans rA et stocke l'adresse actuelle dans rB (utile pour les appels de fonctions).

Le jeu d'instruction RiSC-16 (machine 16 bits à 8 registres)...

 

instruction format risc16

... et le codage binaire correspondant !

 

La confusion est souvent faite entre l'architecture d'un processeur et son jeu d'instruction. Les deux éléments sont a priori indépendants. Par exemple, il est théoriquement tout à fait possible de concevoir un processeur haute performance supportant le jeu d'instruction ARMv8, mais cela n'est pas fait pour des raisons commerciales (Intel n'ira jamais supporter un concurrent) ou stratégiques (la rétrocompatibilité est cassée si l'assembleur est changé).

 

Aujourd'hui, rares sont les programmeurs suffisamment aventureux pour se lancer dans la rédaction de bibliothèques en assembleur ; car des langages de programmation bien plus facile à comprendre ont été développés depuis. Cependant, pour un optimisation maximale des programmes, certaines boucles peuvent utiliser des intrinsics, c'est à dire de l'assembleur inséré dans du code. Mais il ne faut pas oublier que - quel que soit le langage utilisé - le processeur ne comprend, in fine, que son assembleur personnel.

 

Exemple : Compter jusqu'à 10 en assembleur RiSC-16

On va utiliser l'assembleur RiSC-16 vu précédemment, qui possède 8 registres. Le registre r0 n'est accessible qu'en lecture seule et contient toujours la valeur 0 par convention, les 7 autre registres sont libres. Pour les entier signés, on prend une convention de codage en complément à 2. Le programme suivant "compte" de 0 à 10 via r1 :

 

         addi r2, r0, 10           # Le registre r2 contient 10
         addi r1, r0, 0            # Le registre r1 contient 0
loop:    beq r1, r2, endloop       # Si r1 = r2, aller à endloop:
         addi r1, r1, 1            # Incrémenter r1
         beq r0, r0, loop          # Aller à loop:
endloop: halt                      # Stop

 

On notera l'utilisation de labels, par exemple "loop:" afin de simplifier la compréhension des instructions de saut ("beq r0, r0, loop" est plus simple à comprendre que "beq r0, r0, -2" !).

 

Grâce à la table ci-dessus, on peut transcrire à la main ce programme en binaire :

001 010 000 0001010
001 001 000 0000000
110 001 010 0000010
001 001 001 0000001
110 000 000 1111101 
#en codage au complément à 2, -3 s'écrit 1111101 (= 125 en écriture non signée) halt

 

Et comme le binaire est un peu trop long, on utilise plutôt l'hexadécimal, le programme se résume alors à :

280A 2400 C502 2481 C07D

 

Maintenant que nous avons vu la base du langage compris par un CPU, passons aux mécanismes internes permettant de l'exécuter, et ce, le plus rapidement possible.

 



Un poil avant ?

Adoption de FreeSync : enfin sur XBox One (MAJ)

Un peu plus tard ...

Enfin un test pour le Quadstellar

Les 14 ragots
Les ragots sont actuellement
ouverts à tous, c'est open bar !