Inconvénients de RISC-V

Au départ, j'ai écrit ce document il y a plusieurs années, en tant qu'ingénieur principal de vérification d'exécution dans ARM. Bien sûr, mon opinion a été influencée par un travail approfondi avec les cœurs exécutifs de différents processeurs. Alors faites-le pour un rabais, s'il vous plaît: peut-être que je suis trop catégorique.

Cependant, je crois toujours que les créateurs de RISC-V pourraient faire beaucoup mieux. D'un autre côté, si j'avais conçu un processeur 32 bits ou 64 bits aujourd'hui, j'aurais probablement implémenté une telle architecture pour tirer parti des outils existants.

L'article décrivait à l'origine le jeu d'instructions RISC-V 2.0. Pour la version 2.2, il a fait quelques mises à jour.

Avant-propos original: quelques opinions personnelles


Le jeu d'instructions RISC-V a été réduit au minimum absolu. Une grande attention est accordée à la minimisation du nombre d'instructions, à la normalisation du codage, etc. Ce désir de minimalisme a conduit à une fausse orthogonalité (comme la réutilisation de la même instruction pour les transitions, les appels et les retours) et à la verbosité obligatoire, qui gonfle à la fois la taille et la quantité. instructions.

Par exemple, voici le code C:

int readidx(int *p, size_t idx) { return p[idx]; } 

Il s'agit d'un cas simple d'indexation d'un tableau, une opération très courante. Voici la compilation pour x86_64:

 mov eax, [rdi+rsi*4] ret 

ou BRAS:

 ldr r0, [r0, r1, lsl #2] bx lr // return 

Cependant, pour RISC-V, le code suivant est requis:

 slli a1, a1, 2 add a0, a1, a1 lw a0, a0, 0 jalr r0, r1, 0 // return 

Simplification RISC-V simplifie le décodeur (c'est-à-dire le frontal du processeur) en exécutant plus d'instructions. Mais la mise à l'échelle de la largeur du pipeline est un problème difficile, tandis que le décodage d'instructions légèrement (ou fortement) irrégulières est bien implémenté (la principale difficulté survient lorsqu'il est difficile de déterminer la longueur de l'instruction: cela est particulièrement évident dans le jeu d'instructions x86 avec de nombreux préfixes).

La simplification de l'ensemble d'instructions ne doit pas être limitée. L'enregistrement et l'ajout de registre avec un décalage de la mémoire de registre est une instruction simple et très courante dans les programmes, et il est très facile pour le processeur de l'implémenter efficacement. Si le processeur n'est pas en mesure d'implémenter directement l'instruction, il peut être relativement facile de la décomposer en ses composants; il s'agit d'un problème beaucoup plus simple que de fusionner des séquences d'opérations simples.

Il faut distinguer les instructions spécifiques «complexes» des processeurs CISC - instructions compliquées, rarement utilisées et inefficaces - des instructions «fonctionnelles» communes aux processeurs CISC et RISC, qui combinent une petite séquence d'opérations. Ces derniers sont utilisés fréquemment et avec des performances élevées.

Mise en œuvre médiocre


  • Extensibilité presque illimitée. Bien que ce soit l'objectif de RISC-V, il crée un écosystème fragmenté et incompatible qui doit être géré avec une extrême prudence.
  • La même instruction ( JALR ) est utilisée pour les appels et les retours, ainsi que pour les branches à registre indirect, où un décodage supplémentaire est requis pour la prédiction de branche
    • Appel: Rd = R1
    • Retour: Rd = R0 , Rs = R1
    • Transition indirecte: Rd = R0 , RsR1
    • (Transition étrange: RdR0 , RdR1 )
  • Le codage de longueur variable du champ d'enregistrement ne se synchronise pas automatiquement (cela est courant - par exemple, un problème similaire avec x86 et Thumb-2 - mais cela provoque divers problèmes d'implémentation et de sécurité, par exemple, la programmation orientée vers l'arrière, c'est-à-dire les attaques ROP )
  • RV64I nécessite une extension de caractères pour toutes les valeurs 32 bits. Cela conduit au fait que la moitié supérieure des registres 64 bits devient impossible à utiliser pour stocker des résultats intermédiaires, ce qui conduit à un placement spécial inutile de la moitié supérieure des registres. Il est plus optimal d'utiliser l'extension avec des zéros (car elle réduit le nombre de commutateurs et peut généralement être optimisée en suivant le bit «zéro» lorsque la moitié supérieure est connue pour être zéro)
  • La multiplication est facultative. Bien que les blocs de multiplication rapide puissent occuper une surface assez importante sur de minuscules cristaux, vous pouvez toujours utiliser des circuits légèrement plus lents qui utilisent activement l'ALU existante pour plusieurs cycles de multiplication.
  • LR / SC des exigences de progression strictes pour un sous-ensemble limité d'applications. Bien que cette restriction soit assez stricte, elle crée potentiellement des problèmes pour les petites implémentations (en particulier sans cache)
    • Cela semble être un remplacement pour l'instruction CAS, voir le commentaire ci-dessous
  • Les bits de mémoire vive FP et le mode d'arrondi sont dans le même registre. Cela nécessite la sérialisation du canal FP si l'opération RMW est effectuée pour changer le mode d'arrondi.
  • FP instructions FP sont codées pour une précision de 32, 64 et 128 bits, mais pas 16 bits (ce qui est beaucoup plus courant dans le matériel que 128 bits)
    • Cela peut être facilement résolu: le code de dimension 0b10 gratuit.
    • Mise à jour: l' espace réservé décimal est apparu dans la version 2.2, mais il n'y a pas d'espace réservé demi-précision. L'esprit est incompréhensible.
  • La façon dont les valeurs FP sont représentées dans le fichier de registre FP n'est pas définie, mais observable (via chargement / stockage)
    • Les auteurs d'émulation vous détesteront
    • La migration des machines virtuelles peut devenir impossible
    • Mise à jour: la version 2.2 nécessite des valeurs de boxe NaN plus larges

Mauvais


  • Il n'y a pas de codes de condition et, à la place, les instructions de comparaison et de branche sont utilisées. Ce n'est pas un problème en soi, mais les conséquences sont désagréables:
    • Espace de codage réduit dans les branches conditionnelles en raison de la nécessité de coder un ou deux spécificateurs de registre
    • Pas de choix conditionnel (utile pour les transitions très imprévisibles)
    • Pas de report / soustraction avec report ou emprunt
    • (Notez que c'est toujours mieux que des ensembles de commandes qui écrivent des drapeaux dans le registre général, puis basculent vers les drapeaux reçus)
  • Il semble que des compteurs de haute précision (cycles matériels) soient requis dans une ISA non privilégiée. En pratique, leur fournir des applications est un excellent vecteur d'attaques sur des canaux tiers
  • La multiplication et la division font partie de la même extension, et il semble que si l'une est implémentée, l'autre devrait l'être également. La multiplication est beaucoup plus simple que la division et est courante sur la plupart des processeurs, mais pas la division.
  • Il n'y a pas d'instructions atomiques dans l'architecture du jeu d'instructions de base. Les microcontrôleurs multicœurs sont de plus en plus courants, de sorte que les instructions atomiques comme LL / SC sont peu coûteuses (pour une implémentation minimale dans un seul processeur [multicœur], un seul bit d'état de processeur est nécessaire)
  • LR / SC ont la même extension que les instructions atomiques plus complexes, ce qui limite la flexibilité pour les petites implémentations
  • Les instructions atomiques générales (pas LR / SC ) n'incluent pas la primitive CAS
    • Le CmpHi:CmpLo d'éviter la nécessité d'une instruction qui lit cinq registres ( CmpHi:CmpLo , CmpHi:CmpLo , SwapHi:SwapLo ), mais cela imposera probablement moins de surcharge de mise en œuvre que le LR / SC vers l'avant garanti, qui est fourni comme remplacements
  • Des instructions atomiques sont proposées qui fonctionnent sur des valeurs 32 bits et 64 bits, mais pas sur des valeurs 8 bits ou 16 bits
  • Pour RV32I, il n'y a aucun moyen de transférer la valeur DP FP entre un entier et un fichier de registre FP, sauf via la mémoire, c'est-à-dire qu'à partir de registres entiers 32 bits, il est impossible de créer un nombre à virgule flottante double précision 64 bits, vous devez d'abord écrire la valeur intermédiaire dans la mémoire et charger lui dans le fichier de registre à partir de là
  • Par exemple, l'instruction ADD 32 bits dans RV32I et l' ADD 64 bits dans RVI64 ont les mêmes encodages, et dans RVI64 un autre encodage ADD.W est ADD.W . Il s'agit d'une complication inutile pour un processeur qui implémente les deux instructions - il serait préférable d'ajouter un nouveau codage 64 bits à la place.
  • Aucune instruction MOV . Le code mnémonique de la commande MV est traduit par l'assembleur dans l'instruction MV rD, rS -> ADDI rD, rS, 0 . Les processeurs hautes performances optimisent généralement les instructions MOV , tout en réorganisant largement les instructions. Une instruction avec un opérande direct de 12 bits a été choisie comme forme canonique de l'instruction MV dans RISC-V.
    • En l'absence de MOV l'instruction ADD rD, rS, r0 devient en fait préférable au MOV canonique, car elle est plus facile à décoder, et les opérations avec registre nul (r0) dans le CPU sont généralement optimisées

Horrible


  • JAL dépense 5 bits pour coder le registre de communication, qui est toujours égal à R1 (ou R0 pour les transitions)
    • Cela signifie que le RV32I utilise un déplacement de branche 21 bits. Cela ne suffit pas pour les grandes applications - par exemple, les navigateurs Web - sans utiliser plusieurs séquences de commandes et / ou «îlots de branche»
    • Il s'agit d'une détérioration par rapport à la version 1.0 de l'architecture de commande!
  • Malgré le grand effort de codage uniforme, les instructions de chargement / stockage sont codées différemment (la casse et les champs immédiats changent)
    • Apparemment, l'orthogonalité de codage du registre de sortie était préférable à l'orthogonalité de codage de deux instructions fortement liées. Ce choix semble un peu étrange étant donné que la génération d'adresse est plus critique en temps.
  • Il n'y a pas d'instructions de chargement de mémoire avec des décalages de registre ( Rbase + Roffset ) ou des index ( Rbase + Rindex << Scale ).
  • FENCE.I implique une synchronisation complète du cache d'instructions avec tous les référentiels précédents, avec ou sans clôturé. Les implémentations doivent soit effacer tous les I $ de la clôture, soit rechercher D $ et le tampon de stockage
  • Dans RV32I, la lecture des compteurs 64 bits nécessite la lecture de la moitié supérieure deux fois, la comparaison et la ramification dans le cas d'un transfert entre la moitié inférieure et la moitié supérieure pendant une opération de lecture
    • En règle générale, les ISA 32 bits incluent une instruction de lecture de registre de paire spéciale pour éviter ce problème.
  • Il n'y a pas d'espace défini de manière architecturale pour le codage des indices, de sorte que les instructions de cet espace ne provoquent pas d'erreur sur les processeurs plus anciens (traités comme NOP ), mais font quelque chose sur les processeurs les plus modernes
    • Des exemples typiques de conseils NOP purs sont des choses comme le rendement de spinlock
    • Les nouveaux processeurs ont également des astuces plus sophistiquées (avec des effets secondaires visibles sur les nouveaux processeurs; par exemple, les instructions de vérification des bordures x86 sont encodées dans l'espace d'index afin que les binaires restent rétrocompatibles)

Source: https://habr.com/ru/post/fr461785/


All Articles