Les FPGA sont construits selon une philosophie très différente des circuits programmables simples et des CPLD étudiés dans la section précédente.
Un FPGA est construit comme une matrice de blocs logiques interconnectés. Ces blocs portent différents noms selon les constructeurs :
Chaque bloc contient des cellules logiques qui peuvent être configurées séparément pour réaliser des fonctions logiques particulières.
Sur la figure ci-dessus, nous avons représenté une architecture à trois niveaux telle qu’on peut la rencontrer dans les FPGA du constructeur Xilinx.
Des blocs d’entrée/sortie (IOB pour Input/Output Block) permettent de configurer la connexion entre les blocs logiques et les broches du circuit.
Les blocs logiques et les blocs d’entrée/sortie sont reliés à un réseau de routage configurable qui peut être très sophistiqué sur les FPGA récents.
Dans cette section, nous prenons pour exemple la famille de FPGA Artix-7 de Xilinx que nous utiliserons dans les activités pratiques. Dans les composants de cette famille, chaque bloc logique est composé de deux tranches (slices). Il existe différents types de tranches que nous ne décrirons pas toutes en détail.
La figure ci-dessus représente le schéma d’une tranche SLICEL (source Xilinx Inc. : UG474 – 7 series FPGAs Configurable Logic Blocks User Guide). Elle contient :
Une LUT réalise une fonction logique combinatoire dont on connaît la table de vérité. Elle repose sur le même principe qu’une PROM (Programmable Read-Only Memory) : une LUT est une mémoire, typiquement accessible en lecture seule, qui associe une valeur à chaque combinaison de son entrée adresse.
Dans les FPGA Artix-7 de Xilinx, une LUT est construite autour d’une RAM de 64 bits, dont le contenu est initialisé au démarrage. Elle peut être utilisée de deux manières différentes :
O5
est inutilisée.A6
est forcée à 1.Ci-dessous, nous utilisons une LUT pour vérifier si deux vecteurs et de trois bits sont égaux. Voici un extrait de la table de vérité et le schéma correspondant :
→ | |||||||
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | → | 1 |
0 | 0 | 0 | 0 | 0 | 1 | → | 0 |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
0 | 0 | 1 | 0 | 0 | 0 | → | 0 |
0 | 0 | 1 | 0 | 0 | 1 | → | 1 |
0 | 0 | 1 | 0 | 1 | 0 | → | 0 |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
0 | 1 | 0 | 0 | 0 | 1 | → | 0 |
0 | 1 | 0 | 0 | 1 | 0 | → | 1 |
0 | 1 | 0 | 0 | 1 | 1 | → | 0 |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
0 | 1 | 1 | 0 | 1 | 0 | → | 0 |
0 | 1 | 1 | 0 | 1 | 1 | → | 1 |
0 | 1 | 1 | 1 | 0 | 0 | → | 0 |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
1 | 0 | 0 | 0 | 1 | 1 | → | 0 |
1 | 0 | 0 | 1 | 0 | 0 | → | 1 |
1 | 0 | 0 | 1 | 0 | 1 | → | 0 |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
1 | 0 | 1 | 1 | 0 | 0 | → | 0 |
1 | 0 | 1 | 1 | 0 | 1 | → | 1 |
1 | 0 | 1 | 1 | 1 | 0 | → | 0 |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
1 | 1 | 0 | 1 | 0 | 1 | → | 0 |
1 | 1 | 0 | 1 | 1 | 0 | → | 1 |
1 | 1 | 0 | 1 | 1 | 1 | → | 0 |
⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
1 | 1 | 1 | 1 | 1 | 0 | → | 0 |
1 | 1 | 1 | 1 | 1 | 1 | → | 1 |
Dans cet autre exemple, nous utilisons les deux sorties d’une LUT pour calculer la somme de deux entiers et sur deux bits (ce circuit ne produira pas le bit de retenue) :
→ | ||||||
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | → | 0 | 0 |
0 | 0 | 0 | 1 | → | 0 | 1 |
0 | 0 | 1 | 0 | → | 1 | 0 |
0 | 0 | 1 | 1 | → | 1 | 1 |
0 | 1 | 0 | 0 | → | 0 | 1 |
0 | 1 | 0 | 1 | → | 1 | 0 |
0 | 1 | 1 | 0 | → | 1 | 1 |
0 | 1 | 1 | 1 | → | 0 | 0 |
1 | 0 | 0 | 0 | → | 1 | 0 |
1 | 0 | 0 | 1 | → | 1 | 1 |
1 | 0 | 1 | 0 | → | 0 | 0 |
1 | 0 | 1 | 1 | → | 0 | 1 |
1 | 1 | 0 | 0 | → | 1 | 1 |
1 | 1 | 0 | 1 | → | 0 | 0 |
1 | 1 | 1 | 0 | → | 0 | 1 |
1 | 1 | 1 | 1 | → | 1 | 0 |
Dans l’utilisation typique d’une LUT, sa RAM de configuration est accessible en écriture au moment d’initialiser son contenu, et en lecture seule au cours de son fonctionnement.
Cependant, certaines LUT peuvent également fournir un accès en écriture à tout moment. On peut alors utiliser un assemblage de LUT, non plus pour réaliser des fonctions combinatoires, mais pour mémoriser des données au cours du fonctionnement du circuit.
Par exemple, dans un FPGA Artix-7, on peut réaliser une RAM de 1024 octets (8192 bits) en utilisant 128 LUT.
On parle alors de RAM distribuée, par opposition à des blocs de RAM spécialisés qui sont également disponibles dans de nombreux FPGA (voir plus bas).
En interne, dans une LUT les éléments de mémorisation sont câblés en chaîne pour former un registre à décalage.
Si votre application utilise de nombreux, ou de longs registres à décalage, les bascules D disponibles dans chaque tranche peuvent être insuffisantes. Dans un FPGA Artix-7, chaque LUT peut être utilisée comme un registre à décalage de 32 bits.
Comme nous venons de le voir, les blocs logiques sont très polyvalents. Ils suffisent à réaliser un grand nombre de fonctions numériques, mais ont l’inconvénient d’être peu optimisés pour des usages spécifiques.
L’utilisation de LUT pour effectuer des opérations mathématiques, comme par exemple des multiplications, ou comme RAM distribuée est possible mais coûteuse et peu performante : dans les deux cas, le circuit occupera un grand nombre de LUT et sera beaucoup plus lent qu’un circuit multiplieur ou RAM optimisé.
Pour cette raison, en plus des blocs logiques, les FPGA peuvent contenir des composants dédiés comme :
Aujourd’hui, les FPGA permettent de réaliser des systèmes complexes pour lesquels une réalisation à la main serait trop difficile. Le développement d’un tel système s’appuie sur des langages de modélisation et des outils automatiques en suivants ces étapes :
Nous illustrons ces étapes en nous appuyant sur l’exemple de la partie commande du robot autonome. La conception a déjà été réalisée dans le chapitre sur les automates.
Le langage VHDL est abordé plus loin dans ce document et un chapitre est consacré à la description des automates. Nous n’entrerons pas dans le détail ici.
Voici le code VHDL décrivant la partie commande du robot autonome, sous la forme d’une machine de Mealy associée à un compteur :
entity RobotAutonome is
generic(
DUREE_DE_ROTATION : positive
);
port(
clk, reset : in std_logic;
ca, cg, cd : in std_logic;
mga, mgr, mda, mdr : out std_logic
);
end RobotAutonome;
architecture Automate of RobotAutonome is
signal fr : std_logic;
signal tr_reg : natural range 0 to DUREE_DE_ROTATION - 1;
type State is (RM, SM, RG, RD);
signal state_reg : State;
begin
p_state_reg : process(clk, reset)
begin
if reset = '1' then
state_reg <= RM;
elsif rising_edge(clk) then
case state_reg is
when RM =>
if ca = '1' then
state_reg <= RD;
elsif cg = '1' then
state_reg <= RG;
end if;
when SM =>
if cg = '0' then
state_reg <= RG;
elsif ca = '1' then
state_reg <= RD;
end if;
when RG | RD =>
if fr = '1' then
state_reg <= RM;
end if;
end case;
end if;
end process p_state_reg;
mga <= not ca when state_reg = RM or state_reg = SM else
not fr when state_reg = RD else '0';
mda <= not ca when state_reg = RM or state_reg = SM else
not fr when state_reg = RG else '0';
mgr <= not fr when state_reg = RG else '0';
mdr <= not fr when state_reg = RD else '0';
p_tr_reg : process(clk)
begin
if rising_edge(clk) then
case state_reg is
when RM | SM =>
tr_reg <= 0;
when RG | RG =>
if fr = '1' then
tr_reg <= 0;
else
tr_reg <= tr_reg + 1;
end if;
end case;
end if;
end process p_tr_reg;
fr <= '1' when tr_reg = DUREE_DE_ROTATION - 1 else '0';
end Automate;
À partir du code source ci-dessus, l’outil de synthèse de Xilinx produit
un premier schéma dans lequel les instructions VHDL ont été traduites
directement par des composants logiques :
des portes logiques (RTL_INV
, RTL_OR
), des multiplexeurs (RTL_MUX
),
un additionneur (RTL_ADD
), des registres (RTL_REG
), une table
de correspondance (RTL_ROM
).
Le schéma après synthèse est composé de 13 LUT, de 10 bascules D (FDRE
)
et de buffers d’entrée/sortie (IBUF
, OBUF
) :
Le schéma suivant représente la disposition des composants utilisés dans le FPGA (en bleu). Nous observons que le circuit utilise quatre tranches réparties dans deux blocs. À titre d’exemple, nous avons affiché les connexions en entrée/sortie d’une LUT.
Par curiosité, nous pouvons visualiser le routage des signaux (en vert sur la figure ci-dessous). À droite, nous observons deux régions rectangulaires avec de nombreuses connexions qui s’entrecroisent. Elles correspondent à des matrices de connexions configurables (switch box) qui relient les blocs logiques entre eux.
En pratique, nous avons rarement l’occasion de visualiser ces schémas qui sont peu lisibles et difficiles à mettre en relation avec le code source VHDL dont ils sont issus.
Les FPGA actuellement disponibles utilisent trois types de technologies pour réaliser les connexions configurables : de la RAM statique (SRAM), de la mémoire Flash ou des antifusibles.
La plupart des constructeurs proposent des FPGA à base de SRAM. Leur principal défaut est qu’ils perdent leur configuration lorsqu’ils ne sont pas alimentés. Pour contourner cette difficulté, ils sont généralement conçus pour être reliés à une mémoire Flash externe dans laquelle ils lisent leur configuration à chaque mise sous tension.
Les FPGA à base d’antifusibles sont une spécialité du constructeur Microsemi (anciennement Actel). Ils sont utilisés en priorité dans les systèmes avioniques et spatiaux soumis à des rayonnements ionisants, comme les particules alpha qui peuvent altérer le contenu des mémoires. Ils sont également réputés pour être plus rapides et moins consommateurs d’énergie que les FPGA à SRAM.