用 TEXTIO 在 VHDL 测试平台读取 BMP 位图图像
VHDL 测试平台导入图像最简单方式是用 BMP 光栅图形格式。BMP 文件被 Windows 原生支持,且以无压缩方式暴露原始像素数据,极适合仿真。
本文讲解如何读取二进制 BMP 文件、将像素数据存入动态内存、送入 VHDL DUT、并将处理后图像写回新 BMP 文件。示例 DUT 执行灰度转换,但技术适用于任何逐像素处理模块。
属于 TEXTIO 系列:
为何 BMP 是 VHDL 最优格式
JPEG 和 PNG 网络普及,但都用压缩。JPEG 有损,PNG 无损;两者都给测试平台直接访问像素数据增加复杂层。BMP 以无压缩光栅图形存储图像数据,每像素用原始 RGB 字节表示。这省去了 VHDL 中解压代码,保证确定性仿真时间。
Photoshop 或 GIMP 等常见图像编辑器内部以光栅数据加载图像。对 VHDL,手动(或脚本)将源图像转 BMP 是最直接途径。
准备 BMP 文件
用 Windows 画图确保一致文件布局:
- 打开图像。
- 选 文件 > 另存为。
- 选 24‑位位图 (*.bmp; *.dib) 并保存。
此法文件头始终为 54 字节 BITMAPINFOHEADER 变体,RGB24 像素格式。下表列出读取的头部字段。
| 偏移量(十进制) | 大小(字节) | 期望值(十六进制) | 说明 |
|---|---|---|---|
| 0 | 2 | 42 4D | ID 字段 |
| 10 | 4 | 54 (36 00 00 00) | 像素数组偏移 |
| 14 | 4 | 40 (28 00 00 00) | 头部大小 |
| 18 | 4 | 读取值 | 图像宽度(像素) |
| 22 | 4 | 读取值 | 图像高度(像素) |
| 26 | 1 | 01 | 颜色平面数 |
| 28 | 1 | 18 | 每像素位数 |
仅绿底字段必需;其余作完整性检查。
测试用例
下面是最小灰度 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 像素波音 747 BMP)可在链接表单提供邮箱下载。
导入 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 行填充至 4 字节倍数。计算每行填充并为图像分配动态存储。
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 文件显示原始波音 747 图像的灰度版。文章显示 JPEG 预览;完整 BMP 文件可通过下载表单获取。
进一步考量
- 高吞吐图像处理可考虑 YUV 编码,将亮度 (Y) 与色度 (U/V) 分离。RGB 与 YUV 互转简单且常更硬件友好。
- CMYK 转换更复杂,因不存在一对一像素映射;通常需查找表或更复杂算法。
- 自定义编码时,可设计轻量文件格式(如
.yuv或.cmyk)省去 BMP 头部,简化解析。 - 自动化 BMP 转换步骤(如用 ImageMagick
convert命令)确保仿真运行间测试向量一致。
VHDL
- 全息摄影揭秘:从加博尔的诺贝尔奖发明到未来数据存储
- NASA 兰利研究中心推出先进全光场相机实现 3D 彩色成像
- Linux下用数字签名验证二进制文件完整性:ELF签名实战脚本详解
- 掌握 C# using 指令:命名空间导入、别名与静态导入全解析
- C语言文件处理完全指南:fopen、fprintf、fread 与 fseek
- C 语言头文件详解:类型、用法与最佳实践
- 精通PSL形式化验证VHDL模块:从理论到实战
- 用 VHDL TEXTIO 库从文本文件初始化 FPGA 块 RAM
- 掌握 Verilog 文件 I/O:打开、读取、写入和关闭
- Python JSON:编码、解码与文件处理实战指南
- 掌握 Java 文件操作:java.io 包下的创建、读取、写入与删除
- Java BufferedReader 读取文件:实战指南与示例