ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA入门项目:基于ROM存储的VGA图片显示设计

FPGA入门项目:基于ROM存储的VGA图片显示设计 简介面向FPGA入门与进阶学习者这份资源演示了如何用Verilog将64×64像素图片预存入ROM并通过VGA接口实时显示。整个工程基于Quartus环境包含完整源码与配置文件Verilog文件.v实现ROM读取与VGA控制器AHDL文件.tdf补充逻辑设计存储器初始化文件.mif存放经MATLAB转换的图片像素RGB数据仿真波形.vwf便于时序验证.sof/.pof可用于直接下载至开发板。压缩包共245个文件大小1.12MB既有工程配置.qpf/.qsf也有综合报告结构清晰。已有1052人学习内容覆盖图片数据生成、ROM建模、VGA行场同步时序控制以及时钟信号产生等关键环节。借助MATLAB生成mif、Quartus综合布线以及硬件下载验证可系统掌握从图片数据到显示屏输出的完整流程适合电子设计与FPGA爱好者对照实践。1. 一块入门板子最容易出成就感的小项目最近整理实验室的FPGA开发板翻出一个吃灰已久的ego1板卡顺手把当年入门时写的“ROM存储图片的VGA显示”项目重新跑了一遍。这个项目可以说是FPGA图像显示方向最经典的练手题目之一简单、直观、见效快整个过程涉及ROM的初始化、VGA时序驱动、像素数据的读取与同步等多个基础知识点非常适合刚学完Verilog语法、想上手一个完整项目的同学。先说清楚这个项目是干什么的把一张图片预先存储到FPGA内部的ROM只读存储器中然后通过VGA接口将图片内容实时显示到显示器上。整个过程不需要外部存储器芯片也不需要复杂的SDRAM控制逻辑仅靠FPGA内部的块RAM资源和几个简单的时序模块就能完成。对于初学者来说这是理解“存储-读取-显示”这条数据链路的最佳方式。项目本身适合三类人一是刚学完Verilog基础语法、想练手完整工程的初学者二是准备找工作、想在手头积累几个拿得出手的FPGA项目的应届生三是对图像显示方向感兴趣、想搞明白VGA时序原理的电子爱好者。2. 整体设计思路为什么选ROM而不是RAM或外部Flash2.1 项目核心架构解析整个系统的数据流其实非常简单VGA驱动模块产生行场同步信号和像素时钟同时生成当前像素位置对应的ROM地址ROM根据地址输出存储的图像数据数据经过简单的同步处理后直接送到VGA的RGB引脚上。核心就这三件事但这三件事背后各有各的门道。先说为什么用ROM存储图片。ROM的特点是上电后数据固定不变适合存储静态内容。在FPGA中ROM一般由内部的块RAM初始化而成通过加载一个COE或MIF格式的初始化文件在综合时直接把数据固化到存储单元里。这样设计的好处是不需要额外的写控制逻辑读取操作简单时序容易把控。与之对比如果用RAM还得考虑上电后如何把图片数据写入RAM的问题这就会引入串口、SD卡或者Flash控制器的设计复杂度瞬间上了一个台阶。而如果选用外部Flash虽然能存大图但初学阶段涉及SPI接口、Flash读时序等额外知识容易让人迷失在细节里。2.2 图片分辨率与存储空间的匹配计算选定了用内部ROM接下来就是算存储空间。以我用的ego1开发板为例其FPGA芯片是Xilinx Artix-7系列的XC7A35T内部有50个Block RAM每个36Kb可以配置成18Kb或36Kb两种模式总存储容量大约1800Kb。我用了一张160x120像素的图片做实验。如果把每个像素的RGB数据都存下来存储量就是160乘以120乘以颜色深度。我选的是RGB332格式即红色3位、绿色3位、蓝色2位共8位数据这样一个像素占1个字节。总存储量就是160乘以120等于19200字节也就是153600比特约150Kb。这个容量对块RAM来说毫无压力只用不到十分之一的资源。如果想让显示效果更细腻可以选择640x480分辨率的图片但此时存储量会飙升到640乘以480乘以8等于2457600比特约2.4Mb超过了XC7A35T内部块RAM的容量上限。所以初学者想完整显示一帧图像建议先用低分辨率图片跑通流程后续再考虑用外部存储器扩展。2.3 为什么需要理解VGA时序VGA显示的底层协议是标准的逐行扫描时序这个时序如果搞不明白整个项目就会陷入看得到信号但画面不正常的困境。VGA接口的核心是五根信号线行同步HSYNC、场同步VSYNC、红色R、绿色G、蓝色B。显示器的电子枪从左到右逐行扫描扫描完一行后回到下一行起点扫描完整帧后再回到左上角这个过程中行同步和场同步信号起到“告诉显示器当前扫描位置”的作用。很多初学者容易忽略的是VGA时序中包含同步脉冲、后沿、有效显示区和前沿四个阶段。只有有效显示区的数据才是真正显示到屏幕上的像素其余时间段必须输出黑色或无效数据。如果这一步处理不好做出来的效果往往是画面偏斜、左右跳动或者上下翻滚。以640x480分辨率60Hz刷新率为例行周期是800个像素时钟其中有效显示640个像素同步脉冲96个时钟后沿48个时钟前沿16个时钟。帧周期是525行其中有效显示480行同步脉冲2行后沿33行前沿10行。这些参数都是有标准的不能随意修改。3. 图片数据准备从PNG到COE文件的关键步骤3.1 图片尺寸与格式的取舍实操第一步是准备图片数据。很多人在这步就栽了跟头因为直接用常规图片工具保存的PNG或JPEG文件FPGA是没法直接用的必须转换成ROM初始化文件支持的格式。图片尺寸的选择要综合考量显示效果和存储容量。我建议初学者先用160x120或者320x240分辨率的图片。这里有个经验心得分辨率不一定要完全贴合VGA标准的分辨率因为我们可以通过控制VGA控制器的计数范围只在有效显示区的一部分区域内显示图片其余区域显示黑色背景。比如在640x480的显示区中设定一个从左上角偏移一定距离的位置作为图片起始点这样就能把160x120的图片在屏幕左上角显示出来图片周围是黑色边框效果非常干净。颜色深度的选择同样关键。RGB332虽然只有8位颜色看起来色彩不够丰富但胜在存储效率高、设计简单。如果想让颜色更饱满可以选用RGB44412位色深或RGB56516位色深代价是存储容量直接翻倍或翻三倍。我实际对比过在160x120分辨率下RGB444的显示效果已经相当不错肉眼几乎看不出与原始图片的明显差异而存储量也仅仅增加到了28800字节。3.2 使用Python脚本完成图片到COE的转换选择好图片后就需要写一个小工具把图片转成COE文件。COE是Xilinx Vivado支持的一种初始化文件格式文件内容是16进制表示的存储数据每条数据对应一个存储地址。我自己写了个Python脚本用PIL图像处理库来处理图片。脚本的逻辑非常简单打开图片并调整尺寸遍历每个像素点将RGB值转换为目标颜色深度格式的整数然后按地址顺序写入COE文件。关键代码大致如下from PIL import Image img Image.open(input.png).convert(RGB) img img.resize((160, 120)) with open(image.coe, w) as f: f.write(memory_initialization_radix16;\n) f.write(memory_initialization_vector\n) for y in range(120): for x in range(160): r, g, b img.getpixel((x, y)) # RGB332: 高3位为红色中3位为绿色低2位为蓝色 data ((r 5) 5) | ((g 5) 2) | (b 6) f.write(f{data:02x}) if not (y 119 and x 159): f.write(,\n) else: f.write(;\n)脚本执行完后会生成一个COE文件这个文件里每一行对应ROM中一个地址的初始值。注意COE文件最后必须以分号结尾否则Vivado在加载时会报错这是新手最容易踩的坑之一。4. FPGA内部ROM的生成与例化4.1 使用Vivado IP核生成单端口ROM有了COE文件下一步就是在Vivado中生成ROM的IP核。打开Vivado后在IP Catalog中搜索Block Memory Generator配置成Single Port ROM模式。这里有几个关键参数需要格外注意。端口宽度必须与图片颜色深度一致我用的RGB332就是8位。读深度要严格等于图片像素总数160x120的图片就是19200。填写初始化文件路径时选择刚才生成的COE文件。还有一点容易忽略在Other Options页面确保选中“Load Init File”选项否则ROM内容不会加载。我常用的是Standalone模式这种模式下输出端口会带一个可选的寄存器虽然增加了一级流水线延迟但能有效改善时序收敛在高主频场景下非常有用。对于本项目我建议开启寄存器输出因为VGA像素时钟只有25MHz多一级延迟不会造成显示错位只要在驱动逻辑中同步调整读取地址的时序就行。4.2 地址换算让图片行与VGA行对齐ROM生成之后最关键的一步是地址计算。VGA控制器在扫描到有效显示区时会持续输出行列坐标。我们需要把这些坐标映射到ROM的读地址上。先确定图片在屏幕上的显示起始位置。比如我希望图片显示在左上角起始点设置为8060即图片左上角距离屏幕左边80个像素、上边60行。那么当一个像素位于图片区域内时图片内的相对坐标就是相对X等于当前像素X坐标减80相对Y等于当前像素Y坐标减60。ROM的存储顺序是逐行扫描即先存第一行的160个像素再存第二行的160个像素依次类推。因此ROM地址等于相对Y乘以图片宽度加相对X。以160x120的图片为例第3行第5个像素的地址就是3乘以160加5等于485。这段逻辑在Verilog中的实现非常直观reg [14:0] rom_addr; always (posedge clk) begin if (in_display_region) begin rom_addr (pixel_y - 60) * 160 (pixel_x - 80); end else begin rom_addr 15d0; end end这里的pixel_x和pixel_y是VGA驱动模块产生的行列计数器的当前值。超出图片区域时直接将地址置零同时将输出数据屏蔽为黑色背景。5. VGA驱动模块的核心实现与同步技巧5.1 VGA时序计数器的搭建VGA驱动模块是整个项目的核心。标准做法是用两个计数器。一个像素计数器用来计每行的像素数范围是0到799一个行计数器用来计总行数范围是0到524。像素计数器每来一个像素时钟就加一加到799后归零同时行计数器加一。行计数器加到524后归零表示一帧结束。同步信号在特定计数器范围内拉低。水平同步信号在像素计数器位于656到751之间时输出低电平其余时间输出高电平。垂直同步信号在行计数器位于490到491之间时输出低电平其余时间输出高电平。有效显示区的判断逻辑稍复杂一些。水平方向像素计数器在0到639之间是有效显示区垂直方向行计数器在0到479之间是有效行两者的与关系就是有效显示信号。我在实际写代码时会把参数定义成常量方便后续修改。如果把分辨率从640x480改成800x600只需要修改常量定义和同步信号的边界条件其余逻辑可以保持不变。5.2 ROM数据的时序对齐这个项目最容易出错的地方在于ROM读出数据与VGA时序的同步。前面提到开启了ROM输出寄存器这意味着一发出地址要等两个时钟周期才能采到正确的数据。如果直接把ROM输出接到RGB引脚上图像会整体向右偏移两个像素虽然不仔细看不容易察觉但对于讲究精确的显示项目来说是不可接受的。解决方法是把有效显示信号和行列坐标也打两拍让所有信号保持同步。具体做法是在显示有效信号打拍后的高电平期间将ROM输出直接赋给RGB输出而在打拍后的无效期间将RGB输出全部置零。reg [1:0] display_valid_delay; always (posedge clk) begin display_valid_delay {display_valid_delay[0], in_display_region}; end assign vga_rgb display_valid_delay[1] ? rom_data : 8d0;这是典型的流水线对齐操作在图像处理项目中非常常用。很多人觉得图像显示项目难难点其实不在逻辑复杂度而在于这种时序对齐的细节。一个小细节没对齐画面就是不正常排查起来还特别费劲。5.3 顶层模块与管脚约束顶层模块的编写相对简单就是把VGA驱动模块和ROM模块例化连接起来。VGA驱动模块输出像素坐标给地址计算模块地址计算模块根据坐标生成ROM地址ROM输出数据经过对齐处理后送给VGA接口。管脚约束文件是另一个需要认真对待的部分。VGA接口的时钟、行同步、场同步和RGB信号都要正确分配到开发板上对应的物理引脚。常见的一种错误是把行同步和场同步信号接反导致画面完全无法显示或者疯狂跳动。RGB信号分配错误则会导致颜色错乱比如红色显示成蓝色。另外别忘了检查开发板的VGA接口是否需要额外的使能信号。部分开发板在VGA接口处集成了一颗电平转换芯片需要额外拉高或拉低某个控制引脚才能正常输出。我用的ego1板卡上有一个VGA相关的使能引脚如果不拉高显示接口完全无输出。6. 常见问题与排查技巧实录6.1 问题速查表我把这个项目实践中遇到的高频问题整理成了表格方便后续排查。现象可能原因解决方案显示器显示“无信号”行场同步信号极性错误或频率不匹配检查行同步低电平持续时间是否在标准范围内画面整体向右偏移ROM输出寄存器导致的数据延迟将有效显示信号打拍对齐后再输出RGB画面只有噪点无图像ROM未加载初始化文件或COE格式错误确认IP核中已勾选Load Init File选项图片显示位置不对起始坐标设置错误检查行列计数器的坐标与起始偏移的计算逻辑颜色全部错乱RGB位宽或位序分配错误对照引脚约束文件和RGB332格式逐位排查图片上下颠倒或左右镜像图片存储顺序与地址映射不一致调整地址计算中的坐标方向6.2 调试经验分享调试VGA项目时最有效的工具是逻辑分析仪。用Vivado自带的ILA核抓取像素计数器、行计数器、ROM地址和ROM数据这几个关键信号。抓完之后对照VGA标准时序图逐一比对基本能快速定位问题所在。不过逻辑分析仪也不是万能的因为VGA信号变化频率较高抓取深度有限。我的经验是先跑低频时钟验证逻辑正确再把时钟频率切换到正常的25MHz。具体做法是把像素时钟先设置成1MHz左右的低频这样每个像素周期变长逻辑分析仪能轻松捕获完整时序如果低频下显示正常说明逻辑本身没有问题问题大概率出在时序约束或高速信号完整性上。还有一个值得一提的坑某些显示器对行场同步信号的边沿非常敏感。我调试时遇到过一台老款显示器在640x480分辨率下显示正常但同一套代码跑到部分新款显示器上就显示“输入不支持”。排查后确认不是FPGA逻辑问题而是显示器对同步信号参数的要求更加严格。如果遇到这种情况可以试试调整同步脉冲的宽度或者增加前沿和后沿的时间。7. 更多的扩展方向RUN通这个基础项目后可以做的事情还有很多。最简单的扩展是改用RGB565颜色格式用16位颜色深度显示图片效果会明显提升。更进一步可以把多张图片存入不同的ROM深度段通过按键切换显示内容这就是一个简单的电子相册。如果想把动态图像显示也搞定那么需要在现有架构上增加SDRAM控制器和摄像头接口把摄像头采集的数据写入SDRAM再从SDRAM读出显示到VGA上。整个数据通路的核心思想与本项目完全一致只是存储介质从ROM换成了更灵活的SDRAM。再往上层走可以在VGA显示的基础上叠加字符叠加模块实现字符、汉字、图片的混合显示。工业控制领域中常见的界面显示本质上就是这些技术的组合。根据我个人的经验这个项目值得好好打磨不是跑通就完了要把每一行Verilog代码都理解透彻。VGA时序、ROM初始化、流水线对齐这三个知识点在后续几乎所有图像相关的FPGA项目中都会反复用到。把基础打牢后面做摄像头采集显示、实时图像处理和HDMI输出时就不需要在底层细节上反复踩坑了。最后再分享一个小技巧做VGA相关项目时在显示器上贴几张便签纸标出关键坐标区域比如标注出8060到240180的图片显示区域调试时会直观很多。别笑这招在排查显示偏移问题时真能节省不少时间。本文还有配套的精品资源点击获取
返回列表