Lire des images bitmap BMP dans les bancs de test VHDL avec TEXTIO
Lorsqu'on travaille avec des bancs de test VHDL, la façon la plus simple d'importer une image est d'utiliser le format graphique raster BMP. Les fichiers BMP sont nativement supportés par Windows, et ils exposent les données brutes des pixels sans compression, les rendant idéaux pour la simulation.
Cet article explique comment lire un fichier BMP binaire, stocker ses données de pixels en mémoire dynamique, les fournir à un DUT VHDL, et écrire l'image traitée dans un nouveau fichier BMP. Le DUT exemple effectue une conversion en niveaux de gris, mais la technique s'applique à tout module de traitement pixel par pixel.
Partie d'une série TEXTIO :
- Initialiser la RAM depuis un fichier avec TEXTIO
- Lire un fichier de stimulus dans le banc de test avec TEXTIO
Pourquoi le BMP est le format optimal pour VHDL
JPEG et PNG sont omniprésents sur le web, mais les deux emploient la compression. JPEG est avec pertes, PNG est sans pertes ; les deux ajoutent une couche de complexité quand un banc de test doit accéder aux données de pixels directement. Le BMP, en revanche, stocke les données d'image comme des graphiques raster non compressés, donc chaque pixel est représenté par des octets RGB bruts. Cela élimine le besoin de code de décompression en VHDL et garantit un temps de simulation déterministe.
Les éditeurs d'images courants comme Photoshop ou GIMP chargent les images en mémoire comme des données raster en interne. Pour VHDL, convertir l'image source en BMP manuellement (ou via un script) est l'approche la plus directe.
Préparation du fichier BMP
Utilisez Windows Paint pour assurer une disposition de fichier cohérente :
- Ouvrez l'image.
- Sélectionnez Fichier > Enregistrer sous.
- Choisissez Bitmap 24 bits (*.bmp; *.dib) et enregistrez.
Avec cette méthode, l'en-tête de fichier sera toujours la variante BITMAPINFOHEADER de 54 octets avec un format de pixel RGB24. Le tableau suivant liste les champs d'en-tête que nous lisons.
| Offset (Déc) | Taille (O) | Attendu (Hex) | Description |
|---|---|---|---|
| 0 | 2 | 42 4D | Champ ID |
| 10 | 4 | 54 (36 00 00 00) | Décalage tableau de pixels |
| 14 | 4 | 40 (28 00 00 00) | Taille d'en-tête |
| 18 | 4 | Lire la valeur | Largeur de l'image en pixels |
| 22 | 4 | Lire la valeur | Hauteur de l'image en pixels |
| 26 | 1 | 01 | Nombre de plans de couleur |
| 28 | 1 | 18 | Nombre de bits par pixel |
Seuls les champs ombragés en vert sont essentiels ; les autres servent de vérifications de bon sens.
Le cas de test
Voici un DUT de conversion en niveaux de gris minimal qui accepte une entrée RGB 24 bits et sort la valeur de luminance correspondante réencodée en RGB. Le module est purement combinatoire, donc aucune horloge n'est requise.
entity grayscale is port ( r_in : in std_logic_vector(7 downto 0); g_in : in std_logic_vector(7 downto 0); b_in : in std_logic_vector(7 downto 0); r_out : out std_logic_vector(7 downto 0); g_out : out std_logic_vector(7 downto 0); b_out : out std_logic_vector(7 downto 0) ); end grayscale;
Pour la complétude, le code source complet du projet (incluant un BMP Boeing 747 de 1000×1000 pixels) est disponible en téléchargement en fournissant une adresse e-mail sur le formulaire lié.
Importation de TEXTIO
Au sommet de votre fichier VHDL, ajoutez :
use std.textio.all; use std.env.finish;
Ces déclarations requièrent VHDL-2008 ou plus récent.
Types personnalisés pour le stockage d'image
Définissez les types suivants dans la région déclarative de votre banc de test :
type header_type is array (0 to 53) of character; type pixel_type is record red : std_logic_vector(7 downto 0); green : std_logic_vector(7 downto 0); blue : std_logic_vector(7 downto 0); end record; type row_type is array (integer range <>) of pixel_type; type row_pointer is access row_type; type image_type is array (integer range <>) of row_pointer; type image_pointer is access image_type;
Ces structures de données dynamiques permettent au banc de test d'accommoder des images de toute taille sans limites de tableau statique.
Instanciation du DUT
signal r_in, g_in, b_in : std_logic_vector(7 downto 0); signal r_out, g_out, b_out : std_logic_vector(7 downto 0); begin DUT : entity work.grayscale port map ( r_in => r_in, g_in => g_in, b_in => b_in, r_out => r_out, g_out => g_out, b_out => b_out );
Lecture de l'en-tête BMP
Le processus commence par charger l'en-tête de 54 octets dans une variable header et valider les champs clés avec des instructions assert. Cette garde empêche l'utilisation accidentelle d'un format de fichier non supporté.
for i in header_type'range loop read(bmp_file, header(i)); end loop; -- Vérifications de base assert header(0) = 'B' and header(1) = 'M' report "First two bytes are not ""BM"". This is not a BMP file" severity failure; assert character'pos(header(10)) = 54 and character'pos(header(11)) = 0 and character'pos(header(12)) = 0 and character'pos(header(13)) = 0 report "Pixel array offset is not 54 bytes" severity failure; assert character'pos(header(14)) = 40 and character'pos(header(15)) = 0 and character'pos(header(16)) = 0 and character'pos(header(17)) = 0 report "DIB header size is not 40 bytes, is this a Windows BMP?" severity failure; assert character'pos(header(26)) = 1 and character'pos(header(27)) = 0 report "Color planes is not 1" severity failure; assert character'pos(header(28)) = 24 and character'pos(header(29)) = 0 report "Bits per pixel is not 24" severity failure;
Après validation, la largeur et la hauteur de l'image sont extraites :
image_width := character'pos(header(18)) + character'pos(header(19)) * 2**8 + character'pos(header(20)) * 2**16 + character'pos(header(21)) * 2**24; image_height := character'pos(header(22)) + character'pos(header(23)) * 2**8 + character'pos(header(24)) * 2**16 + character'pos(header(25)) * 2**24; report "image_width: " & integer'image(image_width) & ", image_height: " & integer'image(image_height);
Lecture des données de pixels
Chaque ligne BMP est remplie jusqu'à un multiple de quatre octets. Calculez le remplissage par ligne et allouez un stockage dynamique pour l'image.
padding := (4 - image_width*3 mod 4) mod 4; image := new image_type(0 to image_height - 1); for row_i in 0 to image_height - 1 loop row := new row_type(0 to image_width - 1); for col_i in 0 to image_width - 1 loop read(bmp_file, char); row(col_i).blue := std_logic_vector(to_unsigned(character'pos(char), 8)); read(bmp_file, char); row(col_i).green := std_logic_vector(to_unsigned(character'pos(char), 8)); read(bmp_file, char); row(col_i).red := std_logic_vector(to_unsigned(character'pos(char), 8)); end loop; for i in 1 to padding loop read(bmp_file, char); end loop; -- ignorer le remplissage image(row_i) := row; end loop;
Test du DUT
Alimentez chaque pixel dans le DUT, attendez un petit cycle delta, et capturez la sortie en niveaux de gris.
for row_i in 0 to image_height - 1 loop row := image(row_i); for col_i in 0 to image_width - 1 loop r_in <= row(col_i).red; g_in <= row(col_i).green; b_in <= row(col_i).blue; wait for 10 ns; row(col_i).red := r_out; row(col_i).green := g_out; row(col_i).blue := b_out; end loop; end loop;
Écriture du BMP de sortie
D'abord, écrivez l'en-tête préservé. Ensuite, sérialisez les données de pixels ligne par ligne, en ajoutant le remplissage si nécessaire. Finalement, nettoyez la mémoire dynamique et fermez les fichiers.
for i in header_type'range loop write(out_file, header(i)); end loop; for row_i in 0 to image_height - 1 loop row := image(row_i); for col_i in 0 to image_width - 1 loop write(out_file, character'val(to_integer(unsigned(row(col_i).blue)))); write(out_file, character'val(to_integer(unsigned(row(col_i).green)))); write(out_file, character'val(to_integer(unsigned(row(col_i).red)))); end loop; deallocate(row); for i in 1 to padding loop write(out_file, character'val(0)); end loop; end loop; deallocate(image); file_close(bmp_file); file_close(out_file); report "Simulation done. Check ""out.bmp"" image."; finish; end process;
Image résultante
Le fichier out.bmp généré montre la version en niveaux de gris de l'image originale du Boeing 747. Un aperçu JPEG est affiché dans l'article ; le fichier BMP complet est disponible via le formulaire de téléchargement.
Considérations supplémentaires
- Pour le traitement d'image à haut débit, envisagez l'encodage YUV où la luminance (Y) est séparée de la chrominance (U/V). La conversion entre RGB et YUV est simple et souvent plus conviviale pour le matériel.
- La conversion CMJN est plus complexe car une correspondance pixel à pixel n'existe pas ; elle nécessite généralement une table de recherche ou un algorithme plus élaboré.
- Lorsqu'on travaille avec des encodages personnalisés, on peut concevoir un format de fichier léger (ex:
.yuvou.cmyk) qui omet l'en-tête BMP entièrement, simplifiant l'analyse. - Automatiser l'étape de conversion BMP (par ex., via la commande
convertd'ImageMagick) assure des vecteurs de test cohérents à travers les exécutions de simulation.
VHDL
- Maîtriser les directives using C# : Imports, alias et directives static
- Gestion des fichiers en C : Maîtriser les E/S avec fopen, fprintf, fread et fseek
- Maîtriser les opérations sur les fichiers Java avec java.io – Création, lecture, écriture et suppression
- Initialiser la RAM bloc FPGA depuis des fichiers texte avec la bibliothèque TEXTIO de VHDL
- Lecture de Fichiers en Java avec BufferedReader – Guide Pratique avec Exemples
- Python JSON : encodage, décodage et gestion de fichiers – Guide pratique
- Maîtriser les entrées/sorties de fichiers en Verilog : Ouvrir, Lire, Écrire et Fermer
- Comprendre les fichiers d'en-tête C : Types, utilisation et bonnes pratiques
- Centre de recherche Langley de la NASA dévoile une caméra plénoptique avancée pour imagerie 3D couleur