Чтение BMP-изображений в VHDL-тестбенчах с помощью TEXTIO
При работе с VHDL-тестбенчами простейший способ импорта изображения — использование растрового формата BMP. BMP-файлы нативно поддерживаются Windows и предоставляют сырые пиксельные данные без сжатия, что идеально для симуляции.
Эта статья объясняет, как прочитать бинарный BMP-файл, сохранить его пиксельные данные в динамической памяти, подать их в VHDL DUT и записать обработанное изображение обратно в новый BMP-файл. Пример DUT выполняет преобразование в оттенки серого, но техника применима к любому пиксельно-ориентированному модулю обработки.
Часть серии TEXTIO:
- Инициализация RAM из файла с помощью TEXTIO
- Чтение стимулирующего файла в тестбенче с помощью TEXTIO
Почему BMP — оптимальный формат для VHDL
JPEG и PNG повсюду в вебе, но оба используют сжатие. JPEG с потерями, PNG без потерь; оба добавляют слой сложности, когда тестбенч должен обращаться к пиксельным данным напрямую. BMP, с другой стороны, хранит данные изображения как несжатую растровую графику, поэтому каждый пиксель представлен сырыми RGB-байтами. Это устраняет необходимость в коде декомпрессии на VHDL и гарантирует детерминированное время симуляции.
Обычные редакторы изображений, такие как Photoshop или GIMP, загружают изображения в память как растровые данные внутренне. Для VHDL ручная конвертация исходного изображения в BMP (или через скрипт) — самый прямой подход.
Подготовка BMP-файла
Используйте Windows Paint для обеспечения согласованной структуры файла:
- Откройте изображение.
- Выберите File > Save As.
- Выберите 24‑bit Bitmap (*.bmp; *.dib) и сохраните.
Этим методом заголовок файла всегда будет 54-байтным вариантом BITMAPINFOHEADER с пиксельным форматом RGB24. Следующая таблица перечисляет поля заголовка, которые мы читаем.
| Offset (Dec) | Size (B) | Expected (Hex) | Description |
|---|---|---|---|
| 0 | 2 | 42 4D | ID field |
| 10 | 4 | 54 (36 00 00 00) | Pixel array offset |
| 14 | 4 | 40 (28 00 00 00) | Header size |
| 18 | 4 | Read value | Image width in pixels |
| 22 | 4 | Read value | Image height in pixels |
| 26 | 1 | 01 | Number of color planes |
| 28 | 1 | 18 | Number of bits per pixel |
Только зеленые поля обязательны; остальные служат проверками здравого смысла.
Тестовый случай
Ниже приведен минимальный DUT в оттенках серого, принимающий 24-битный RGB-вход и выводящий соответствующее значение яркости, закодированное обратно в RGB. Модуль чисто комбинационный, такт не требуется.
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;
Для полноты, полный исходный проект (включая 1000×1000 пикселей BMP Boeing 747) доступен для скачивания, указав email в связанной форме.
Импорт TEXTIO
В начале вашего VHDL-файла добавьте:
use std.textio.all; use std.env.finish;
Эти объявления требуют VHDL‑2008 или новее.
Пользовательские типы для хранения изображения
Определите следующие типы в декларативной области вашего тестбенча:
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;
Эти динамические структуры данных позволяют тестбенчу вмещать изображения любого размера без статических ограничений массивов.
Инстанциация 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 );
Чтение заголовка BMP
Процесс начинается с загрузки 54-байтового заголовка в переменную header и проверки ключевых полей с операторами assert. Эта защита предотвращает случайное использование неподдерживаемого формата файла.
for i in header_type'range loop read(bmp_file, header(i)); end loop; -- Basic sanity checks 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;
После валидации извлекаются ширина и высота изображения:
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);
Чтение пиксельных данных
Каждая строка BMP дополняется до кратности четырём байтам. Вычислите отступ на строку и выделите динамическое хранилище для изображения.
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; -- discard padding image(row_i) := row; end loop;
Тестирование DUT
Подайте каждый пиксель в DUT, подождите небольшой дельта-цикл и захватите выход в оттенках серого.
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;
Запись выходного BMP
Сначала запишите сохраненный заголовок. Затем сериализуйте пиксельные данные по строкам, добавляя отступы где необходимо. Наконец, очистите динамическую память и закройте файлы.
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;
Результирующее изображение
Сгенерированный файл out.bmp показывает версию в оттенках серого исходного изображения Boeing 747. JPEG-превью отображено в статье; полный BMP-файл доступен через форму скачивания.
Дальнейшие соображения
- Для высокопроизводительной обработки изображений рассмотрите YUV-кодирование, где яркость (Y) отделена от хроматических компонент (U/V). Конвертация между RGB и YUV проста и часто более дружелюбна к железу.
- CMYK-конвертация сложнее, потому что не существует один-к-один отображения пикселей; обычно требует таблицы поиска или более сложного алгоритма.
- При работе с кастомными кодировками можно спроектировать легкий формат файла (например,
.yuvили.cmyk), опускающий BMP-заголовок полностью, упрощая парсинг. - Автоматизация шага конвертации BMP (например, через команду
convertImageMagick) гарантирует согласованные тестовые векторы между запусками симуляции.
VHDL
- Голография: от нобелевского открытия Габора к хранилищам данных будущего
- Освоение операторов using в C#: импорт, псевдонимы и статические директивы
- Работа с файлами в C: мастерство ввода-вывода с fopen, fprintf, fread и fseek
- Мастерство файловых операций в Java с java.io: создание, чтение, запись и удаление
- Инициализация блочной памяти FPGA из текстовых файлов с помощью библиотеки TEXTIO в VHDL
- Чтение файлов в Java с BufferedReader — Практическое руководство с примерами
- Python JSON: кодирование, декодирование и работа с файлами — практическое руководство
- Работа с файлами в Verilog: открытие, чтение, запись и закрытие
- Заголовочные файлы в C: типы, подключение и лучшие практики
- Научный центр NASA Langley представляет передовительную пленоптическую камеру для 3D-цветовой визуализации