Veuillez consulter cette page à partir de l'interface principale.
Les multiplications
yasep/doc/multiply.fr.html version 2012-5-1

Introduction

Actuellement, l'architecture du YASEP définit des instructions de multiplication partielles. Initialement, l'idée était d'accéder un multiplieur multi-cycles au travers des Registres Spéciaux mais cela aurait rendu les changements de contextes trop complexes.

La méthode actuelle calcule un produit en assemblant les résultats de multiplications partielles. C'est un compromis entre la taille du code, la vitesse, la complexité matérielle et le multitasking. D'autres versions pourront utiliser des multiplieurs complets (au besoin, et si la technologie le permet) mais pour l'instant, le système réutilise l'additionneur 16 bits comme étage final, après deux additionneurs partiels de 12 bits, chacun additionnant les résultats sur 8 bits de quatre tables de multiplications 4×4 bits.

Cette méthode ajoute dynamiquement un étage au pipeline, chaque fois qu'une instruction MUL8L ou MUL8H est exécutée. En conséquence, une "bulle" d'un cycle est insérée dans le pipeline lorsque l'instruction suivante n'est pas une multiplication. Morale : il faut grouper les MULs.

Il y a 2 instructions de multiplication : MUL8L et MUL8H. Elles sont identiques mais MUL8H prend l'octet de poids fort de l'opérande SND, au lieu de l'octet de poids faible pour MUL8L. Cela simplifie un peu les séquences de code, en économisant quelques instructions de décalage ou rotation. (voir l'exemple R16×R16 plus bas).

Seules les multiplications non signées sont supportées pour l'instant. Les multiplicandes signés doivent être ajustés manuellement avant et après les séquences d'instructions.

Quelques exemples de multiplications

R8 × R8

; R1(8 bits) × R2(8 bits) => R2(16 bits)
  MUL8L R1 R2 ; 2 octets, instruction courte
; R1(8 bits) × R2(8 bits) => R3(16 bits)
  MUL8L R1 R2 R3 ; 4 octets, instruction longue

R8 × I8

; R1(8 bits) × Imm16(8 bits) => R2(16 bits)
  MUL8L 123 R1 R2 ; 4 octets
; R1(8 bits) × Imm16(8 bits) => R1(16 bits)
  MUL8L 123 R1 ; est un alias vers :
  MUL8L 123 R1 R1 ; 4 octets aussi
; R1(8 bits) × Imm4(4 bits) => R1(12 bits)
  MUL8L 12 R1 ; 2 octets

R8 × R16

YASEP16 : 6 instructions

; R1(8 bits) × R2(16 bits) => R3(16 LSB)-R4(8 MSB)
; R5 = temporaire

; moitié haute
  MUL8H R1 R2 R3
  SHR 8 R3 R4 ; répartit le résultat entre R3 et R4
  SHL 8 R3
; moitié basse
  MUL8L R1 R2 R5
  ADD R5 R3
    ADD 1 R4 R4 carry

R8 × I16

; R1(8 bits) × Imm16 => R3(16 LSB)-R4(8 MSB)
; R2 = temporaire
; Imm16=1234h dans ces exemple

; moitié haute
  MUL8L 12h R1 R3 ; 12h = 1234h>>8
  SHR 8 R3 R4 ; répartit le résultat entre R3 et R4
  SHL 8 R3

; moitié basse
  MUL8L 34h R1 R2 ; 34h = 1234h & 0xFF
  ADD R2 R3
    ADD 1 R4 R4 carry

I8 × R16

; (8 bits) × R2(16 bits) => R3(16 LSB)-R4(8 MSB)
; R5 = temporaire
; Imm8 = 12h dans cet exemple

; moitié haute
  MUL8H 12h R2 R3
  SHR 8 R3 R4 ; répartit le résultat entre R3 et R4
  SHL 8 R3
; moitié basse
  MUL8L 12h R2 R5
  ADD R5 R3
    ADD 1 R4 R4 carry

R16 × R16

YASEP16 : 13 instructions

; R1 × R2 => R3-R4 (R5=scratch)
; R2 peut rester modifié (rotation) à la fin de cette séquence.

; les 2 octets du milieu sont calculés ensemble
  MUL8H R1 R2 R3
  MUL8H R2 R1 R4 ; observez l'échange des opérandes
  ADD R4 R3 ; on réutilise la retenue plus tard

  SHR 8 R3 R4 ; ajuste entre R3 et R4
  SHL 8 R3
    MOV 100h R5 ; (retenue spéculative)
    OR R4 R5 R4 carry ; et met la retenue dans R4
; octet de poids faible
  MUL8L R2 R1 R5
  ADD R5 R3
    ADD 1 R4 R4 carry
; octet de poids fort
  ROL 8 R2
  MUL8H R2 R1 R5
  ADD R5 R4
; éventuellement :
  ROR 8 R2

Comment initialiser la table de multiplication

La table peut être réalisée avec des portes logiques ou une matrice de diodes spécialiée. Cependant certaines réalisations en FPGA peuvent utiliser des petits blocs de SRAM, en particulier sur cible Actel. Pour réaliser une multiplication de 8×8 bits, il faut disposer de 4 blocs de 4×4=8 bits de résultat. Deux blocs "dual port" de SRAM de 256 octets sont nécessaires. Les 4 tables peuvent être initialisées avec des valeurs identiques donc il n'y a que 256 valeurs à stocker et écrire. C'est réalisé par le code suivant :

; Code d'initialisation de la table de multiplication :
; R1 : SND (première adresse + compteur de boucle externe)
; R2 : SI4 (deuxième adresse (octet bas) + valeur initiale (octet haut))
; R3 : accumulator : incrément de la deuxième adresse + incrément initial (octet haut)
; R4 : compteur de boucle interne, encodée par position de bit (1-hot)
; A1 : adresse de la boucle externe
; A2 : adresse de la boucle interne

 mov 0 R1
 mov 11h R3
 mov 1 R4
 add 4 PC A1
 ; boucle externe, 16 itérations :
   mov 0 R2
   add 4 PC A2
   ; boucle interne, 16 itérations :
     ADD R3 R2
     MULI R2 R1
     ror 1 R4
     mov A2 PC LSB0 R4

   add  100h R3
   add 1011h R1
   mov A1 PC no_carry