Virgule fixe avec fixed_pkg
Comprendre ufixed, sfixed, les bits fractionnaires, resize et la conversion vers std_logic_vector.
Pourquoi la virgule fixe
Un FPGA manipule naturellement des bits. Pour représenter une valeur comme 0.75, -1.25 ou un gain de filtre, on évite souvent la virgule flottante. On utilise plutôt une virgule fixe : un entier binaire avec une position de virgule connue.
Exemple sur 8 bits en Q4.4 :
- 4 bits pour la partie entière ;
- 4 bits pour la partie fractionnaire ;
- la valeur physique est le mot binaire divisé par
2^4.
Le package fixed_pkg
VHDL-2008 fournit fixed_pkg :
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.FIXED_PKG.ALL;Il définit principalement :
| Type | Usage |
|---|---|
ufixed | nombre non signé à virgule fixe |
sfixed | nombre signé à virgule fixe |
Le point important : les indices ne représentent pas seulement des positions de bits. Ils indiquent aussi la position de la virgule.
signal r_gain : ufixed(1 downto -6);Ici :
- indices
1 downto 0: partie entière ; - indices
-1 downto -6: partie fractionnaire ; - largeur totale : 8 bits.
Exemple simple
signal r_a : ufixed(3 downto -4);
signal r_b : ufixed(3 downto -4);
signal r_y : ufixed(4 downto -4);
r_y <= resize(r_a + r_b, r_y'high, r_y'low);r_y a un bit entier de plus que r_a et r_b, afin de garder une éventuelle retenue.
Signé : sfixed
Pour une valeur pouvant être négative, utilisez sfixed.
signal r_x : sfixed(0 downto -11);
signal r_k : sfixed(1 downto -6);
signal r_y : sfixed(2 downto -11);
r_y <= resize(r_x * r_k, r_y'high, r_y'low);La multiplication augmente naturellement la largeur du résultat. resize ramène le résultat au format choisi.
Conversion depuis et vers les ports
Les ports restent souvent en std_logic_vector, surtout pour rester compatibles avec les IP et les outils.
entity fixed_adapter is
port (
i_x : in std_logic_vector(11 downto 0);
o_y : out std_logic_vector(11 downto 0)
);
end entity fixed_adapter;
architecture rtl of fixed_adapter is
signal w_x : sfixed(0 downto -11);
signal w_y : sfixed(0 downto -11);
begin
w_x <= to_sfixed(i_x, w_x'high, w_x'low);
w_y <= resize(w_x, w_y'high, w_y'low);
o_y <= to_slv(w_y);
end architecture rtl;La convention doit être connue des deux côtés : ici, le port i_x transporte un nombre signé avec 1 bit entier et 11 bits fractionnaires.
Constantes et testbench
Les conversions depuis real sont pratiques en testbench ou pour définir des constantes.
constant c_GAIN : sfixed(0 downto -7) := to_sfixed(0.75, 0, -7);En revanche, n'utilisez pas real comme signal RTL pour décrire du matériel. Le real sert à la simulation et aux calculs statiques, pas à un bus synthétisable.
Saturation et arrondi
Avec la virgule fixe, il faut penser à deux problèmes :
- arrondi : que faire des bits fractionnaires supprimés ?
- débordement : que faire si la valeur ne rentre plus ?
fixed_pkg permet de contrôler ces comportements avec resize. Selon les outils, le support exact peut varier, donc gardez les formats simples au début et validez toujours en simulation.
r_y <= resize(r_acc, r_y'high, r_y'low);Si le design est critique, écrivez aussi des tests sur :
- valeur minimale ;
- valeur maximale ;
- valeur négative ;
- transition proche de zéro ;
- résultat qui déborde.
Bonnes pratiques
- Documentez le format : Q1.11, Q4.4, etc.
- Gardez les ports en
std_logic_vectorsi le bloc doit s'intégrer à des IP. - Convertissez vite en
sfixedouufixedà l'intérieur du bloc. - Utilisez
resizeaprès les additions, multiplications et changements de format. - Vérifiez en simulation avec des valeurs limites.
À retenir
| Besoin | Choix |
|---|---|
| Valeur positive fractionnaire | ufixed |
| Valeur signée fractionnaire | sfixed |
| Adapter un format | resize |
| Convertir depuis un port | to_sfixed ou to_ufixed |
| Convertir vers un port | to_slv |
| Calculs réels de testbench | real, pas RTL synthétisable |
📝 Tester mes connaissances - Quiz du chapitre