Leçon 19 sur 22

Chapitre 19 — Arithmétique numérique en VHDL

Objectif — Finalité du chapitre

Maîtriser les représentations signées et non signées, les opérations arithmétiques, la virgule fixe et la conception d’une unité arithmétique et logique 8 bits avec indicateurs.

Objectifs pédagogiques

À la fin de ce chapitre, l’étudiant devra être capable de :

  • interpréter un vecteur comme unsigned ou signed ;
  • calculer la plage représentable sur N bits ;
  • expliquer le complément à deux et l’extension de signe ;
  • détecter le report et le débordement signé ;
  • dimensionner les additions, soustractions et multiplications ;
  • favoriser l’utilisation des blocs DSP ;
  • représenter une valeur en virgule fixe QI.F ;
  • réaliser addition, multiplication, troncature et arrondi en virgule fixe ;
  • mettre en œuvre une saturation ;
  • concevoir une UAL 8 bits et ses indicateurs ;
  • vérifier automatiquement les opérations et les cas limites.

Prérequis

  • types signed et unsigned de numeric_std ;
  • opérateurs VHDL et conversions ;
  • processus combinatoires ;
  • synthèse logique et blocs DSP ;
  • bancs de test automatisés.

Organisation du chapitre

Section

Contenu

Compétence principale

19.1Représentation des nombresInterpréter et dimensionner
19.2Addition et soustractionGérer report et débordement
19.3MultiplicationDimensionner et exploiter les DSP
19.4Virgule fixeTraiter des nombres fractionnaires
19.5Unité arithmétique et logiqueCombiner opérations et indicateurs
TP 15UAL 8 bitsVérifier et analyser le matériel

 

Concept clé — Les bits n’ont pas de signe intrinsèque

Le même motif binaire peut représenter des valeurs différentes selon qu’il est interprété comme unsigned, signed ou nombre en virgule fixe.

 

19.1 Représentation des nombres

L’arithmétique VHDL exige de choisir explicitement le type numérique et la largeur. Cette décision détermine la plage, les conversions et le comportement des opérateurs.

19.1.1 Entiers non signés

Un unsigned de N bits représente les entiers de 0 à 2^N - 1.

Largeur N

Minimum

Maximum

4 bits015
8 bits0255
12 bits04 095
16 bits065 535

 

Déclaration non signée  |  VHDL
signal compteur : unsigned(7 downto 0);
signal adresse  : unsigned(11 downto 0);

 

19.1.2 Conversion non signée

Conversions usuelles  |  VHDL
u <= unsigned(vecteur_slv);

vecteur_slv <= std_logic_vector(u);

u <= to_unsigned(150, u'length);

valeur_entiere := to_integer(u);

 

19.1.3 Entiers signés

Le type signed de numeric_std utilise le complément à deux. Sur N bits, la plage est -2^(N-1) à 2^(N-1)-1.

Largeur N

Minimum

Maximum

4 bits-87
8 bits-128127
12 bits-2 0482 047
16 bits-32 76832 767

 

Déclaration signée  |  VHDL
signal temperature : signed(11 downto 0);
signal erreur      : signed(15 downto 0);

 

19.1.4 Même motif, deux valeurs

Motif 8 bits

unsigned

signed

0000010155
01111111127127
10000000128-128
11111111255-1
11110110246-10

 

19.1.5 Complément à deux

Pour coder -x sur N bits, on inverse les bits du code de x puis on ajoute 1, en conservant N bits.

Étape pour -5 sur 8 bits

Valeur

+500000101
Inversion11111010
Ajout de 111111011
Interprétation signed-5

 

Négation signée  |  VHDL
resultat <= -signed(entree);

 

19.1.6 Valeur minimale particulière

Sur 8 bits signés, -128 est représentable, mais +128 ne l’est pas. La négation de la valeur minimale déborde si la largeur reste inchangée.

Attention — Asymétrie

La plage signée contient une valeur négative supplémentaire : -128 à 127 sur 8 bits.

 

19.1.7 Extension par zéro

Extension d’un unsigned  |  VHDL
u_large <= resize(u_court, u_large'length);

-- Équivalent conceptuel :
u_large <= "0000" & u_court;

 

Les nouveaux bits de poids fort valent zéro.

19.1.8 Extension de signe

Pour conserver la valeur d’un signed, les nouveaux bits doivent recopier le bit de signe.

Extension signée  |  VHDL
s_large <= resize(s_court, s_large'length);

-- Exemple manuel :
s_large <= (7 downto 4 => s_court(3))
           & s_court;

 

19.1.9 Exemple d’extension

Valeur

4 bits

Extension à 8 bits

+5010100000101
-3110111111101
-8100011111000

 

19.1.10 Réduction de largeur

La réduction supprime des bits et peut modifier la valeur. resize conserve les bits de poids faible pour unsigned et maintient le bit de signe selon les règles du type signed, mais ne garantit pas l’absence de dépassement.

Réduction explicite  |  VHDL
sortie8 <= entree16(7 downto 0);

 

Dimensionnement — Vérifier avant de réduire

Comparer la valeur aux limites de la destination, puis choisir troncature, arrondi, saturation ou signalement d’erreur.

 

19.1.11 Débordement non signé

Un débordement non signé se produit lorsque le résultat dépasse 2^N-1. Un bit supplémentaire permet de conserver le report.

Addition étendue  |  VHDL
somme_etendue <= ('0' & a)
                    + ('0' & b);

resultat <= somme_etendue(N-1 downto 0);
report   <= somme_etendue(N);

 

19.1.12 Débordement signé

Un débordement signé apparaît lorsque le résultat mathématique sort de la plage, et non simplement lorsqu’un report est produit.

Opération 8 bits

Résultat binaire

Report

Débordement signé

127 + 110000000 (-128)0Oui
-128 - 101111111 (127)0Oui
-1 + 1000000001Non
100 + 2001111000 (120)0Non

 

19.1.13 Détection par les signes

  • Addition : opérandes de même signe et résultat de signe différent.
  • Soustraction : opérandes de signes différents et résultat de signe différent de a.

19.1.14 Valeur absolue

Valeur absolue signée  |  VHDL
magnitude <= abs(valeur_signee);

 

Le cas de la valeur minimale doit être traité avec une largeur supplémentaire si sa magnitude positive doit être représentée.

19.1.15 Littéraux et largeur

Constantes typées  |  VHDL
constant MAX_U8 : unsigned(7 downto 0)
    := to_unsigned(255, 8);

constant MIN_S8 : signed(7 downto 0)
    := to_signed(-128, 8);

 

19.2 Addition et soustraction

Les opérateurs + et - sont définis par numeric_std pour signed et unsigned. La largeur du résultat doit être planifiée explicitement.

19.2.1 Additionneur parallèle

Un additionneur parallèle calcule tous les bits d’une addition dans un même bloc combinatoire. Sur FPGA, les chaînes de retenue dédiées accélèrent ce calcul.

Addition N bits  |   VHDL
somme <= a + b;

 

Si somme possède la même largeur que a et b, le report final est perdu.

19.2.2 Addition avec report

Somme sur N+1 bits  |   VHDL
signal ext_a, ext_b : unsigned(N downto 0);
signal ext_somme    : unsigned(N downto 0);

ext_a <= resize(a, N+1);
ext_b <= resize(b, N+1);
ext_somme <= ext_a + ext_b;

somme <= ext_somme(N-1 downto 0);
carry <= ext_somme(N);

 

19.2.3 Addition avec retenue d’entrée

Addition a + b + cin  |   VHDL
process(all)
    variable total : unsigned(N downto 0);
begin
    total := resize(a, N+1)
           + resize(b, N+1);

    if cin = '1' then
        total := total + 1;
    end if;

    somme <= total(N-1 downto 0);
    cout  <= total(N);
end process;

 

19.2.4 Débordement de l’addition signée

Formule logique  |  VHDL
overflow_add <=
    (not (a(a'high) xor b(b'high)))
    and
    (resultat(resultat'high) xor a(a'high));

 

Signe a

Signe b

Signe résultat

Overflow

0011
1101
010 ou 10
100 ou 10

 

19.2.5 Soustracteur

Soustraction non signée modulo 2^N  |  VHDL
difference <= a - b;

 

Si a < b, le résultat s’enroule dans la largeur disponible. Un indicateur d’emprunt doit compléter la sortie.

19.2.6 Détection de l’emprunt

Borrow  |   VHDL
borrow <= '1' when a < b else '0';

difference <= a - b;

 

19.2.7 Soustraction par complément à deux

Le matériel peut réaliser a - b comme a + not(b) + 1. Les FPGA exploitent la même chaîne de retenue pour l’addition et la soustraction.

Principe  |   Équation
difference <= a + (not b) + 1;

 

Dans le code RTL, l’opérateur - reste plus clair et permet au synthétiseur de choisir l’implémentation.

19.2.8 Débordement de la soustraction signée

Formule logique  |  VHDL
overflow_sub <=
    (a(a'high) xor b(b'high))
    and
    (resultat(resultat'high) xor a(a'high));

 

19.2.9 Addition saturante non signée

Saturation à 255  |   VHDL
process(all)
    variable total : unsigned(8 downto 0);
begin
    total := ('0' & a) + ('0' & b);

    if total(8) = '1' then
        resultat <= (others => '1');
    else
        resultat <= total(7 downto 0);
    end if;
end process;

 

19.2.10 Addition saturante signée

Saturation 8 bits signée  |  VHDL
process(all)
    variable total : signed(8 downto 0);
begin
    total := resize(a, 9) + resize(b, 9);

    if total > to_signed(127, 9) then
        resultat <= to_signed(127, 8);
    elsif total < to_signed(-128, 9) then
        resultat <= to_signed(-128, 8);
    else
        resultat <= resize(total, 8);
    end if;
end process;

 

19.2.11 Additionneur structurel

Un additionneur peut être construit par une chaîne d’additionneurs complets. Cette description est utile pour comprendre la retenue, mais l’opérateur + est généralement préférable en RTL.

Chaîne de retenue conceptuelle  |  VHDL
GEN_ADD : for i in 0 to N-1 generate
begin
    U_FA : entity work.additionneur_complet(rtl)
        port map (
            a    => a(i),
            b    => b(i),
            cin  => retenue(i),
            s    => somme(i),
            cout => retenue(i+1)
        );
end generate;

 

19.2.12 Comparaison arithmétique

Comparaisons explicites  |  VHDL
inferieur_u <= '1'
    when unsigned(a_slv) < unsigned(b_slv)
    else '0';

inferieur_s <= '1'
    when signed(a_slv) < signed(b_slv)
    else '0';

 

19.2.13 Coût matériel

Fonction

Ressource probable

Addition N bitsChaîne de retenue + LUT
Addition/soustraction sélectionnéeAdditionneur + contrôle XOR/carry
SaturationComparateurs + multiplexeur
OverflowQuelques portes logiques
Addition pipelineAdditionneur + registres

 

19.3 Multiplication

La multiplication combine les bits des opérandes pour produire un résultat dont la largeur complète est la somme des largeurs.

19.3.1 Dimensionnement non signé

Le produit d’un unsigned de NA bits par un unsigned de NB bits nécessite jusqu’à NA + NB bits.

Opérandes

Largeur complète du produit

8 × 8 bits16 bits
12 × 10 bits22 bits
16 × 16 bits32 bits
24 × 18 bits42 bits

 

19.3.2 Multiplication combinatoire

Produit complet  |  VHDL
signal a, b    : unsigned(7 downto 0);
signal produit : unsigned(15 downto 0);

produit <= a * b;

 

19.3.3 Multiplication signée

Produit signé  |   VHDL
signal a, b    : signed(7 downto 0);
signal produit : signed(15 downto 0);

produit <= a * b;

 

Les deux opérandes doivent être interprétés avec le type approprié. Mélanger signed et unsigned nécessite des conversions explicites.

19.3.4 Troncature du produit

Conservation des bits faibles  |  VHDL
resultat8 <= produit(7 downto 0);

 

Cette troncature calcule le produit modulo 256 pour un résultat non signé. Pour une mesure numérique, il faut analyser l’échelle et le risque de dépassement.

19.3.5 Bits forts

Conservation des bits de poids fort  |  VHDL
resultat8 <= produit(15 downto 8);

 

Cette sélection peut être utilisée dans certains redimensionnements en virgule fixe, mais elle change l’échelle.

19.3.6 Utilisation des blocs DSP

Les FPGA possèdent souvent des blocs DSP contenant des multiplicateurs, additionneurs et registres. L’opérateur * permet au synthétiseur de les reconnaître.

Description

Implémentation possible

Petit produit constantDécalages et additions dans les LUT
Produit 8 × 8DSP ou LUT selon la cible
Produit 18 × 18Bloc DSP typique
Multiply-accumulateDSP avec addition intégrée
Produit pipelineRegistres internes du DSP

 

19.3.7 Attribut d’outil

Exemple conceptuel  |  VHDL
attribute use_dsp : string;
attribute use_dsp of produit : signal is "yes";

 

L’attribut est spécifique à l’outil et ne remplace pas la vérification du rapport de synthèse.

19.3.8 Multiplication par constante

Multiplication par 10  |   VHDL
y <= shift_left(x, 3)
   + shift_left(x, 1);  -- 8x + 2x

 

Le synthétiseur peut effectuer automatiquement cette optimisation à partir de y <= x * 10.

19.3.9 Produit-addition

Multiply-accumulate combinatoire  |  VHDL
acc_suivant <= acc + resize(a * b, acc'length);

 

MAC cadencé  |   VHDL
process(clk)
begin
    if rising_edge(clk) then
        if enable = '1' then
            acc <= acc + resize(a * b, acc'length);
        end if;
    end if;
end process;

 

19.3.10 Pipelining du multiplicateur

Produit enregistré  |  VHDL
process(clk)
begin
    if rising_edge(clk) then
        produit <= a * b;
    end if;
end process;

 

Les registres peuvent être absorbés dans le bloc DSP et améliorer la fréquence maximale au prix d’une latence.

19.3.11 Multiplication séquentielle : principe

Une multiplication séquentielle réutilise un additionneur sur plusieurs cycles. Elle réduit la surface mais augmente la latence.

Cycle

Action shift-and-add

Initialaccumulateur = 0
Chaque cycleSi le bit du multiplicateur vaut 1, ajouter le multiplicande décalé
PuisDécaler et avancer au bit suivant
FinProduit disponible après N cycles

 

19.3.12 Registres d’un multiplicateur itératif

Architecture conceptuelle  |  Architecture
-- Registres :
-- multiplicande_decale : 2N bits
-- multiplicateur       : N bits
-- accumulateur         : 2N bits
-- compteur_bits        : log2(N) bits
-- FSM                   : IDLE, CALCUL, FIN

 

19.3.13 Compromis

Architecture

Surface

Latence

Débit

CombinatoireÉlevéeChemin combinatoireUn résultat par changement
DSP pipelineBloc dédié + registresQuelques cyclesSouvent un par cycle
SéquentielleFaibleN cycles ou plusUn résultat par opération

 

19.3.14 Analyse du rapport

  • nombre de blocs DSP ;
  • nombre de LUT si le produit est distribué ;
  • registres de pipeline ;
  • largeur réelle des opérateurs ;
  • fréquence maximale et chemin critique.

19.4 Virgule fixe

La virgule fixe représente une valeur réelle par un entier mis à l’échelle. Elle est efficace sur FPGA car elle utilise des opérateurs entiers.

19.4.1 Convention QI.F

Dans ce chapitre, QI.F désigne un mot de I+F bits. Pour une valeur signée, I inclut le bit de signe et F indique le nombre de bits fractionnaires.

Relation valeur brute / valeur réelle  |  Calcul
VALEUR_REELLE = ENTIER_STOCKE
                / 2^F

 

19.4.2 Exemple Q4.4 signé

Q4.4 utilise 8 bits : quatre bits à gauche de la virgule, signe inclus, et quatre bits fractionnaires.

Motif

Entier signed

Valeur réelle

00111000563,5
00011000241,5
11101000-24-1,5
011111111277,9375
10000000-128-8

 

Notation — Conventions variables

Certains documents comptent le bit de signe séparément. Toujours préciser la convention, la largeur totale et le nombre F de bits fractionnaires.

 

19.4.3 Pas de quantification

Le plus petit incrément vaut 2^-F. Pour Q4.4, le pas vaut 1/16 = 0,0625.

F

Pas

40,0625
80,00390625
120,000244140625
160,0000152587890625

 

19.4.4 Conversion d’une constante réelle

Pour stocker x, on calcule idéalement round(x × 2^F), puis on code cet entier.

Exemple 3,25 en Q4.4  |   Calcul
3,25 * 16 = 52
52 decimal = 00110100 binary

 

19.4.5 Déclaration VHDL

Valeurs Q4.4  |   VHDL
subtype q4_4_t is signed(7 downto 0);

constant UN_ET_DEMI : q4_4_t
    := to_signed(24, 8);  -- 24 / 16 = 1,5

 

19.4.6 Addition en virgule fixe

Deux valeurs ayant le même F peuvent être additionnées directement. Le résultat possède le même nombre de bits fractionnaires.

Addition Q4.4 étendue  |  VHDL
signal somme_q : signed(8 downto 0);

somme_q <= resize(a_q, 9)
         + resize(b_q, 9);

 

Le bit supplémentaire protège contre un débordement immédiat. Une saturation ou réduction doit ensuite être choisie.

19.4.7 Alignement de formats différents

Avant d’additionner QI1.F1 et QI2.F2, les points binaires doivent être alignés, généralement en augmentant le nombre de bits fractionnaires du format le moins précis.

Conversion Q4.4 vers Q4.8  |  VHDL
a_q4_8 <= shift_left(
    resize(a_q4_4, a_q4_8'length),
    4
);

 

19.4.8 Multiplication en virgule fixe

Le produit d’un QI1.F1 par un QI2.F2 possède F1+F2 bits fractionnaires et une largeur complète égale à la somme des largeurs.

Q4.4 × Q4.4  |   VHDL
signal produit_q8_8 : signed(15 downto 0);

produit_q8_8 <= a_q4_4 * b_q4_4;

 

19.4.9 Retour vers Q4.4

Pour retrouver quatre bits fractionnaires, le produit Q8.8 est décalé arithmétiquement de quatre positions, puis réduit.

Troncature après multiplication  |  VHDL
signal produit_decale : signed(15 downto 0);

produit_decale <= shift_right(produit_q8_8, 4);

resultat_q4_4 <= resize(produit_decale, 8);

 

resize seul ne réalise pas une saturation. Les bits supprimés doivent être contrôlés si le dépassement est interdit.

19.4.10 Troncature

La troncature supprime les bits fractionnaires faibles. Elle est simple mais introduit une erreur orientée vers les valeurs inférieures pour les nombres positifs.

Valeur exacte

Troncature au pas 0,25

Erreur

1,371,25-0,12
2,992,75-0,24
-1,37Selon règle de décalage signéÀ spécifier

 

19.4.11 Arrondi

Un arrondi au plus proche peut être obtenu en ajoutant un demi-LSB avant la suppression des bits pour les valeurs positives. La règle pour les valeurs négatives doit être explicitement choisie.

Arrondi symétrique simplifié  |  VHDL
constant DEMI_LSB : signed(15 downto 0)
    := to_signed(2**3, 16);  -- F=4

process(all)
    variable p_arrondi : signed(15 downto 0);
begin
    if produit_q8_8 >= 0 then
        p_arrondi := produit_q8_8 + DEMI_LSB;
    else
        p_arrondi := produit_q8_8 - DEMI_LSB;
    end if;

    resultat_etendu <= shift_right(p_arrondi, 4);
end process;

 

Cet exemple réalise un arrondi au plus proche avec les demi-valeurs éloignées de zéro. D’autres politiques existent : vers zéro, vers +∞, vers -∞ ou ties-to-even.

19.4.12 Saturation

Fonction de saturation vers 8 bits  |  VHDL
function saturer_s8(
    x : signed(15 downto 0)
) return signed is
begin
    if x > to_signed(127, 16) then
        return to_signed(127, 8);
    elsif x < to_signed(-128, 16) then
        return to_signed(-128, 8);
    else
        return resize(x, 8);
    end if;
end function;

 

19.4.13 Exemple de chaîne fixe

Multiplier, arrondir, décaler, saturer  |  Méthode
produit_large <= a_q * b_q;

-- Ajout de la correction d'arrondi.
-- Décalage de F bits.
-- Comparaison aux limites de destination.
-- Saturation ou conversion finale.

 

19.4.14 Choix du format

Augmenter I

Augmenter F

Plage plus grandePrécision fractionnaire meilleure
Moins de risque de saturationErreur de quantification réduite
Mot potentiellement plus largeMultiplicateurs et mémoires plus larges
Pas inchangéPlage réduite si largeur totale fixe

 

19.4.15 Bruit de quantification

La différence entre une valeur réelle et sa représentation fixe constitue l’erreur de quantification. Dans une chaîne de calcul, les erreurs peuvent s’accumuler.

19.4.16 Bit de garde

Des bits supplémentaires internes, appelés bits de garde, limitent les pertes intermédiaires. La réduction est effectuée à la sortie de la chaîne.

Accumulateur élargi  |  VHDL
signal accumulateur : signed(23 downto 0);

accumulateur <= accumulateur
             + resize(produit, accumulateur'length);

 

19.4.17 Bibliothèques fixed_pkg

VHDL-2008 propose des paquetages de virgule fixe dans IEEE. Leur disponibilité et leur synthèse dépendent de l’outil. numeric_std avec une convention explicite reste très portable.

Méthode — Vérification

Les calculs en virgule fixe doivent être comparés à un modèle de référence logiciel et testés aux valeurs minimales, maximales et proches des seuils d’arrondi.

 

19.5 Unité arithmétique et logique

Une UAL regroupe plusieurs fonctions arithmétiques et logiques. Un code d’opération sélectionne le résultat et les indicateurs décrivent son état.

19.5.1 Opérations proposées

op

Fonction

Indicateur C

000a AND b0
001a OR b0
010a XOR b0
011NOT a0
100a + bReport
101a - bEmprunt : 1 si a < b
110Décalage gauche de aBit a(7) éjecté
111Décalage droit logique de aBit a(0) éjecté

 

19.5.2 Indicateur zéro Z

Détection de zéro  |   VHDL
z <= '1'
    when resultat = x"00"
    else '0';

 

19.5.3 Indicateur signe N

Bit de signe  |   VHDL
n <= resultat(7);

 

N indique seulement le bit de poids fort. Son interprétation comme signe suppose un résultat signé.

19.5.4 Indicateur C

Dans ce chapitre, C signifie report pour l’addition, emprunt pour la soustraction et bit éjecté pour les décalages. Sa convention doit être documentée.

19.5.5 Indicateur V

V signale un débordement en arithmétique signée pour ADD et SUB. Il vaut zéro pour les opérations logiques et les décalages de cette UAL.

19.5.6 Entité de l’UAL

ual8.vhd — Entité  |  VHDL
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity ual8 is
    port (
        a, b     : in   std_logic_vector(7 downto 0);
        op       : in   std_logic_vector(2 downto 0);
        resultat : out std_logic_vector(7 downto 0);
        z        : out std_logic;
        c        : out std_logic;
        n        : out std_logic;
        v        : out std_logic
    );
end entity ual8;

 

19.5.7 Architecture : variables par défaut

ual8.vhd — Déclarations du processus  |  VHDL
architecture rtl of ual8 is
begin
    process(all)
        variable r_v    : std_logic_vector(7 downto 0);
        variable c_v    : std_logic;
        variable v_v    : std_logic;
        variable ext_u  : unsigned(8 downto 0);
    begin
        r_v := (others => '0');
        c_v := '0';
        v_v := '0';

 

19.5.8 Architecture : opérations logiques

ual8.vhd — Opérations 000 à 011  |  VHDL
        case op is
            when "000" =>
                r_v := a and b;

            when "001" =>
                r_v := a or b;

            when "010" =>
                r_v := a xor b;

            when "011" =>
                r_v := not a;

 

19.5.9 Architecture : addition

ual8.vhd — Addition  |  VHDL
            when "100" =>
                ext_u := ('0' & unsigned(a))
                       + ('0' & unsigned(b));

                r_v := std_logic_vector(
                    ext_u(7 downto 0)
                );
                c_v := ext_u(8);

                v_v := (not (a(7) xor b(7)))
                       and (r_v(7) xor a(7));

 

19.5.10 Architecture : soustraction

ual8.vhd — Soustraction  |  VHDL
            when "101" =>
                r_v := std_logic_vector(
                    unsigned(a) - unsigned(b)
                );

                if unsigned(a) < unsigned(b) then
                    c_v := '1';
                else
                    c_v := '0';
                end if;

                v_v := (a(7) xor b(7))
                       and (r_v(7) xor a(7));

 

19.5.11 Architecture : décalages et sorties

ual8.vhd — Fin du case  |   VHDL
            when "110" =>
                r_v := a(6 downto 0) & '0';
                c_v := a(7);

            when others =>
                r_v := '0' & a(7 downto 1);
                c_v := a(0);
        end case;

        resultat <= r_v;
        c <= c_v;
        v <= v_v;

        if r_v = x"00" then
            z <= '1';
        else
            z <= '0';
        end if;

        n <= r_v(7);
    end process;
end architecture rtl;

 

19.5.12 Décalage arithmétique optionnel

Décalage droit signé  |  VHDL
resultat <= std_logic_vector(
    shift_right(signed(a), 1)
);

 

Le bit de signe est recopié. Cette opération peut remplacer ou compléter le décalage logique selon le cahier des charges.

19.5.13 Rotation optionnelle

Rotation gauche  |  VHDL
resultat <= a(6 downto 0) & a(7);

 

19.5.14 Priorité et combinatoire

L’architecture affecte toutes les variables par défaut et couvre tous les codes. Aucun latch ne doit être inféré.

19.5.15 UAL combinatoire ou enregistrée

UAL combinatoire

UAL enregistrée

Résultat après propagationRésultat capturé au front
Latence inférieure à un cycleLatence d’un cycle ou plus
Chemin critique potentiellement longTiming mieux maîtrisé
Pas de bascules de sortieBascules supplémentaires

 

19.5.16 Pipeline

Une UAL large peut enregistrer les entrées, le résultat ou des étapes internes. La latence augmente mais le débit peut atteindre une opération par cycle.

19.5.17 Ressources attendues

Fonction

Ressource

AND/OR/XOR/NOTLUT
ADD/SUBLUT + chaîne de retenue
Décalage fixe de 1Routage/LUT faible
Sélection opMultiplexeurs/LUT
IndicateursComparateurs et logique légère

 

Méthode de conception arithmétique

1. Définir signed ou unsigned pour chaque donnée.

2. Calculer la plage des entrées.

3. Dimensionner chaque résultat complet.

4. Choisir le comportement en cas de dépassement.

5. Définir report, emprunt et overflow.

6. Fixer le format Q des données fractionnaires.

7. Conserver des bits de garde.

8. Choisir troncature, arrondi et saturation.

9. Simuler les valeurs limites.

10. Examiner DSP, LUT, bascules et chemin critique.

Checklist

  • Les conversions signed/unsigned sont-elles explicites ?
  • La largeur complète du produit est-elle conservée ?
  • Le carry est-il distinct de l’overflow signé ?
  • La convention de l’emprunt est-elle documentée ?
  • Le nombre F de bits fractionnaires est-il connu ?
  • La réduction de largeur est-elle contrôlée ?
  • Les flags Z, C, N et V sont-ils vérifiés ?
  • Le rapport confirme-t-il l’utilisation des DSP attendus ?

Erreurs fréquentes

Erreur

Conséquence

Correction

Utiliser std_logic_vector pour l’arithmétiqueOpérateurs ambigusConvertir vers signed/unsigned
Produit stocké sur 8 bitsPerte des bits fortsRésultat sur 16 bits
Confondre carry et overflowFlags incorrectsDétecter séparément
Oublier l’extension de signeValeur négative modifiéeUtiliser resize(signed)
Additionner des formats Q différentsÉchelles incompatiblesAligner les points
Tronquer sans analyseBiais et dépassementArrondir/saturer
Tester uniquement des valeurs positivesErreurs signées non détectéesCas limites négatifs

 

Travaux pratiques

TP 15 — Conception d’une UAL

1. Objectifs du TP

  • réaliser une UAL combinatoire 8 bits ;
  • sélectionner huit opérations ;
  • gérer Z, C, N et V ;
  • tester automatiquement les opérations ;
  • réaliser des tests exhaustifs pour ADD et SUB ;
  • analyser les ressources matérielles et le chemin critique.

2. Organisation du projet

Arborescence proposée  |  Arborescence
tp15_ual/
├── src/
│   └── ual8.vhd
├── sim/
│   └── tb_ual8.vhd
└── constraints/
    └── carte.xdc_ou_qsf

 

3. Codes d’opération

Code

Opération

Résultat

000ANDa and b
001ORa or b
010XORa xor b
011NOTnot a
100ADDa + b
101SUBa - b
110SHLa << 1
111SHRa >> 1 logique

 

4. Source de l’UAL

Utiliser l’entité et l’architecture complètes présentées dans la section 19.5. Simuler avant la synthèse.

5. Banc de test : déclarations

tb_ual8.vhd — Partie 1  |  VHDL
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use STD.ENV.ALL;

entity tb_ual8 is
end entity tb_ual8;

architecture simulation of tb_ual8 is
    signal a, b : std_logic_vector(7 downto 0)
                  := (others => '0');
    signal op : std_logic_vector(2 downto 0)
                := (others => '0');
    signal resultat : std_logic_vector(7 downto 0);
    signal z, c, n, v : std_logic;

    function entier_s8(x : natural) return integer is
    begin
        if x >= 128 then
            return integer(x) - 256;
        else
            return integer(x);
        end if;
    end function;
begin
    DUT : entity work.ual8(rtl)
        port map (
            a => a, b => b, op => op,
            resultat => resultat,
            z => z, c => c, n => n, v => v
        );

 

6. Procédure de vérification

tb_ual8.vhd — Partie 2  |  VHDL
    stimuli : process
        procedure tester(
            constant code_op : natural;
            constant va      : natural;
            constant vb      : natural;
            constant attendu : natural;
            constant c_att   : std_logic;
            constant v_att   : std_logic
        ) is
            variable z_att : std_logic;
            variable n_att : std_logic;
        begin
            a <= std_logic_vector(to_unsigned(va, 8));
            b <= std_logic_vector(to_unsigned(vb, 8));
            op <= std_logic_vector(to_unsigned(code_op, 3));
            wait for 1 ns;

            if attendu = 0 then
                z_att := '1';
            else
                z_att := '0';
            end if;

            if attendu >= 128 then
                n_att := '1';
            else
                n_att := '0';
            end if;

 

tb_ual8.vhd — Partie 3  |  VHDL
            assert unsigned(resultat)
                   = to_unsigned(attendu, 8)
                report "Resultat incorrect, op="
                       & integer'image(code_op)
                       & ", a=" & integer'image(va)
                       & ", b=" & integer'image(vb)
                severity error;

            assert c = c_att
               and v = v_att
               and z = z_att
               and n = n_att
                report "Indicateur incorrect"
                severity error;
        end procedure;

        variable somme_i : integer;
        variable diff_i  : integer;
        variable signe_a : integer;
        variable signe_b : integer;
        variable signe_r : integer;
        variable c_exp   : std_logic;
        variable v_exp   : std_logic;
    begin

 

7. Tests dirigés

tb_ual8.vhd — Partie 4  |  VHDL
        tester(0, 16#A5#, 16#3C#, 16#24#, '0', '0');
        tester(1, 16#A5#, 16#3C#, 16#BD#, '0', '0');
        tester(2, 16#A5#, 16#3C#, 16#99#, '0', '0');
        tester(3, 16#A5#, 0,       16#5A#, '0', '0');

        tester(6, 16#A5#, 0, 16#4A#, '1', '0');
        tester(7, 16#A5#, 0, 16#52#, '1', '0');

        -- Débordement signé positif.
        tester(4, 100, 50, 150, '0', '1');

        -- -128 - 1 donne 127 avec overflow.
        tester(5, 128, 1, 127, '0', '1');

 

8. Test exhaustif de l’addition

tb_ual8.vhd — Partie 5  |  VHDL
        for va in 0 to 255 loop
            for vb in 0 to 255 loop
                somme_i := va + vb;

                if somme_i > 255 then
                    c_exp := '1';
                else
                    c_exp := '0';
                end if;

                signe_a := entier_s8(va);
                signe_b := entier_s8(vb);
                signe_r := signe_a + signe_b;

                if signe_r > 127 or signe_r < -128 then
                    v_exp := '1';
                else
                    v_exp := '0';
                end if;

                tester(
                    4, va, vb,
                    somme_i mod 256,
                    c_exp, v_exp
                );
            end loop;
        end loop;

 

9. Test exhaustif de la soustraction

tb_ual8.vhd — Partie 6  |  VHDL
        for va in 0 to 255 loop
            for vb in 0 to 255 loop
                diff_i := va - vb;

                if va < vb then
                    c_exp := '1';
                else
                    c_exp := '0';
                end if;

                signe_a := entier_s8(va);
                signe_b := entier_s8(vb);
                signe_r := signe_a - signe_b;

                if signe_r > 127 or signe_r < -128 then
                    v_exp := '1';
                else
                    v_exp := '0';
                end if;

                tester(
                    5, va, vb,
                    diff_i mod 256,
                    c_exp, v_exp
                );
            end loop;
        end loop;

 

10. Fin automatique

tb_ual8.vhd — Partie 7  |  VHDL
        report "UAL : tests diriges et exhaustifs valides"
            severity note;
        stop;
        wait;
    end process;

    timeout : process
    begin
        wait for 200 us;

        assert false
            report "Timeout du TP 15"
            severity failure;
    end process;
end architecture simulation;

 

11. Remarque sur la durée

Les deux tests exhaustifs couvrent 131 072 couples arithmétiques. Avec une attente de 1 ns par cas, le temps simulé dépasse 131 µs. Pour un premier essai, réduire les boucles à quelques valeurs représentatives.

12. Cas limites à observer

Opération

a

b

Résultat

Flags principaux

ADD25510C=1, Z=1
ADD signé1271-128V=1, N=1
SUB01255C(emprunt)=1
SUB signé-1281127V=1
SHL1000000100000010C=1
SHR0000000100000000C=1, Z=1

 

13. Commandes GHDL

Compilation et simulation  |  Terminal
ghdl -a --std=08 src/ual8.vhd
ghdl -a --std=08 sim/tb_ual8.vhd
ghdl -e --std=08 tb_ual8
ghdl -r --std=08 tb_ual8    --vcd=tb_ual8.vcd

gtkwave tb_ual8.vcd

 

14. Synthèse et analyse des ressources

1. Définir ual8 comme entité supérieure.

2. Lancer la synthèse.

3. Ouvrir le schéma RTL.

4. Identifier l’additionneur/soustracteur et le multiplexeur d’opération.

5. Relever les LUT et chaînes de retenue.

6. Vérifier l’absence de latch et de bascule.

7. Consulter le chemin critique.

8. Comparer avec une version enregistrée de l’UAL.

15. Version enregistrée à comparer

Registre de sortie  |  VHDL
process(clk)
begin
    if rising_edge(clk) then
        if reset = '1' then
            resultat_reg <= (others => '0');
            flags_reg <= (others => '0');
        elsif enable = '1' then
            resultat_reg <= resultat_comb;
            flags_reg <= z_comb & c_comb
                       & n_comb & v_comb;
        end if;
    end if;
end process;

 

16. Tableau d’analyse

Indicateur

UAL combinatoire

UAL enregistrée

LUT  
Bascules0 attendu 
Chaîne de retenue  
Chemin critique  
Fréquence maximaleDépend du chemin combinatoire 
LatenceCombinatoire1 cycle

 

17. Questions d’analyse

1. Pourquoi le produit de deux nombres 8 bits nécessite-t-il 16 bits ?

2. Quelle différence existe entre carry et overflow signé ?

3. Pourquoi -128 possède-t-il un cas particulier sur 8 bits ?

4. Quelle extension faut-il appliquer à un signed négatif ?

5. Que représente F dans le format QI.F ?

6. Pourquoi un produit Q4.4 devient-il Q8.8 ?

7. Quelle différence existe entre troncature et arrondi ?

8. Pourquoi la saturation ajoute-t-elle des comparateurs ?

9. Que signifie C lors d’une soustraction dans cette UAL ?

10. Quel matériel principal est attendu pour ADD et SUB ?

18. Extensions proposées

  • Ajouter une comparaison signed et unsigned.
  • Ajouter un décalage arithmétique.
  • Ajouter des rotations.
  • Créer une UAL 16 bits générique.
  • Ajouter une multiplication 8 × 8 avec résultat 16 bits.
  • Créer une addition saturante.
  • Ajouter un mode Q4.4.
  • Pipeline l’UAL en deux étages.
  • Comparer une multiplication LUT et DSP.

19. Barème indicatif

Critère

Points

UAL 8 bits et sélection4
Addition, soustraction et décalages3
Indicateurs Z, C, N et V4
Testbench automatique3
Tests exhaustifs ADD/SUB2
Analyse des ressources2
Comparaison combinatoire/enregistrée1
Qualité du compte rendu1
Total20

 

Synthèse du chapitre

Notion

Résumé

unsignedEntier de 0 à 2^N-1.
signedEntier en complément à deux.
Extension de signeRecopie du bit de signe vers les bits ajoutés.
CarryReport non signé de l’addition.
BorrowEmprunt de la soustraction.
OverflowRésultat signé hors plage.
Produit completLargeur égale à la somme des largeurs.
DSPBloc spécialisé pour multiplication et accumulation.
QI.FFormat fixe avec F bits fractionnaires.
ArrondiRéduction de l’erreur lors du changement d’échelle.
SaturationLimitation aux bornes représentables.
UALBloc sélectionnant plusieurs opérations et indicateurs.

 

Autoévaluation

1. Quelle plage possède unsigned(7 downto 0) ?

Réponse : 0 à 255.

2. Quelle plage possède signed(7 downto 0) ?

Réponse : -128 à 127.

3. Comment étendre un nombre signé ?

Réponse : En recopiant son bit de signe.

4. Carry et overflow sont-ils identiques ?

Réponse : Non.

5. Quelle largeur pour un produit 8 × 8 ?

Réponse : 16 bits.

6. Que vaut le pas d’un Q4.4 ?

Réponse : 1/16 = 0,0625.

7. Que devient F lors d’une multiplication ?

Réponse : Les nombres de bits fractionnaires s’additionnent.

8. Pourquoi arrondir ?

Réponse : Pour réduire l’erreur de quantification.

9. Que fait la saturation ?

Réponse : Elle bloque la valeur aux limites.

10. Que signifie Z dans l’UAL ?

Réponse : Le résultat est nul.

Exercice de consolidation

Calculer la largeur et le format du produit de deux nombres Q3.5 signés sur 8 bits. Proposer le redimensionnement vers Q3.5.

Correction proposée  |  Analyse
-- Q3.5 × Q3.5 :
-- largeur complète : 8 + 8 = 16 bits
-- bits fractionnaires : 5 + 5 = 10
-- format intermédiaire : Q6.10

produit_q6_10 <= a_q3_5 * b_q3_5;

-- Retour à 5 bits fractionnaires :
produit_decale <= shift_right(produit_q6_10, 5);

-- Puis arrondi éventuel, contrôle des limites
-- et saturation vers 8 bits Q3.5.

 

Suite du cours — Transition vers la suite

Après l’arithmétique numérique, le cours peut aborder les interfaces et périphériques courants : UART, SPI, I²C et génération PWM.