FPGA实现VGA接口——保存图片至ROM/RAM显示(以及一些坑的梳理)

mobile365bet365com 📅 2025-10-20 00:06:34 👤 admin 👁️ 6185 ❤️ 39
FPGA实现VGA接口——保存图片至ROM/RAM显示(以及一些坑的梳理)

实验描述:

使用altera芯片FPGA实现VGA接口图像显示,将图片保存在ROM/RAM中,实现静态图片的显示。

这里主要记录自己在调试过程中遇到的一些坑,希望可以让看到的人少走一点弯路,当然,也会附上我的全部代码,供大家参考。

本项目参考了其他博主的文章,再次感谢他们的帮助。

http://dengkanwen.com/70.html

http://t.csdn.cn/7ykBT

过程描述

首先,附上全部各个模块的代码

//VGA彩条显示

//端口定义

module vga_colorbar_top(

input wire sys_clk ,

input wire sys_rst,

output wire vga_hs, // 输出到vga接口的行同步信号

output wire vga_vs, // 输出到vga接口的场同步信号

output wire [15:0] vga_rgb // 输出到vga接口的像素数据

);

//内部信号定义

wire clk_w ;

wire locked_w;

wire [15:0] pixel_data_w;

wire [9:0] pixel_hpos_w;

wire [9:0] pixel_vpos_w;

//wire sys_rst_n;

//assign sys_rst_n = sys_rst && locked_w;

//例化pll

PLL_48MHz_to_25MHz pll_25MHz(

//.areset (sys_rst),//对复位信号取反

.inclk0 (sys_clk),

.c0 (clk_w)

//.locked (locked_w)

);

//vga驱动模块

vga_driver u_vga_driver(

//pll与系统时钟复位输入信号

.clk_25MHz (clk_w),

.rst (sys_rst),

//驱动模块输出信号

.vga_hs (vga_hs),

.vga_vs (vga_vs),

.vga_rgb (vga_rgb),

//显示模块输入像素坐标数据信号

.pixel_hpos (pixel_hpos_w),

.pixel_vpos (pixel_vpos_w),

.pixel_data (pixel_data_w)

);

//vga显示模块

vga_display u_vga_disp(

.clk_25MHz (clk_w),

.rst (sys_rst),

.pixel_hpos (pixel_hpos_w),

.pixel_vpos (pixel_vpos_w),

.pixel_data (pixel_data_w)

);

endmodule

// VGA驱动模块,分辨率640*480@60

module vga_driver(

input wire clk_25MHz, //根据不同分辨率的VGA设定的时钟频率

input wire rst,

input wire [15:0] pixel_data, //RGB--565,即pixel_data[15:11]控制R、pixel_data[10:5]控制G、pixel_data[4:0]控制B

output wire [ 9:0] pixel_hpos, //pixel_data的行坐标

output wire [ 9:0] pixel_vpos, //pixel_data的列坐标

output wire vga_hs, //行同步信号

output wire vga_vs, //列同步信号

output wire [15:0] vga_rgb //输出到VGA接口的颜色数据

);

//内部参量定义,vga时序参数

parameter H_SYNC = 10'd96; // 同步期

parameter H_BACK = 10'd48; // 显示后沿

parameter H_DISP = 10'd640; // 显示区域

parameter H_FRONT = 10'd16; // 显示前沿

parameter H_PRIOD = 10'd800; // 行周期总长度

parameter V_SYNC = 10'd2; // 同步期

parameter V_BACK = 10'd33; // 显示后沿

parameter V_DISP = 10'd480; // 显示区域

parameter V_FRONT = 10'd10; // 显示前沿

parameter V_PRIOD = 10'd525; // 列周期总长度

// 使能信号,用于区分有效数据

wire vga_en;

// 请求信号,用于定位像素带你坐标

wire pixel_data_require;

// VGA行场同步信号计数器

reg [9:0] cnt_h;

reg [9:0] cnt_v;

// VGA行场同步信号生成

assign vga_hs = (cnt_h <= H_SYNC) ? 1'b0 : 1'b1;

assign vga_vs = (cnt_v <= V_SYNC) ? 1'b0 : 1'b1;

// 确定有效区域

assign vga_en = (((cnt_h >= H_SYNC + H_BACK) && (cnt_h < H_SYNC + H_BACK + H_DISP))

&& ((cnt_v >= V_SYNC + V_BACK) &&(cnt_v < V_SYNC + V_BACK + V_DISP)))

? 1'b1 : 1'b0;

// 请求信号比行有效数据先来一个周期,确定有效数据即将到来和即将结束

assign pixel_data_require = (((cnt_h >= H_SYNC + H_BACK - 1'b1) && (cnt_h < H_SYNC + H_BACK + H_DISP - 1'b1)) &&

((cnt_v>= V_SYNC + V_BACK) && (cnt_v < V_SYNC + V_BACK + V_DISP))) ? 1'b1 : 1'b0;

// 确定像素当前坐标

assign pixel_hpos = pixel_data_require ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 10'd0;

assign pixel_vpos = pixel_data_require ? (cnt_v - (V_SYNC + V_BACK - 1'b1)) : 10'd0;

// 确定像素数据

assign vga_rgb = vga_en ? pixel_data:16'd0;

// 行列计数

always @ (posedge clk_25MHz or negedge rst) begin

if (!rst) begin

cnt_h <= 10'd0;

end

else begin

if (cnt_h <= H_PRIOD - 1'b1) begin

cnt_h <= cnt_h + 1'b1;

end

else begin

cnt_h <= 10'd0;

end

end

end

always @ (posedge clk_25MHz or negedge rst) begin

if(!rst)begin

cnt_v <= 10'd0;

end

else begin

if (cnt_h == H_PRIOD - 1'b1) begin

if(cnt_v <= V_PRIOD - 1'b1)

cnt_v <= cnt_v + 1'b1;

else begin

cnt_v <= 10'd0;

end

end

end

end

endmodule

// 显示模块

// 保存图像数据,driver模块从这取数据

module vga_display(

input wire clk_25MHz,

input wire rst,

input wire [9:0] pixel_hpos,

input wire [9:0] pixel_vpos,

output reg [15:0] pixel_data

);

parameter H_DISP =10'd640; // 行分辨率

parameter V_DISP =10'd480; // 列分辨率

localparam WHITE = 16'b11111_111111_11111;

localparam BLACK = 16'b00000_000000_00000;

localparam RED = 16'b11111_000000_00000;

localparam GREEN = 16'b00000_111111_00000;

localparam BLUE = 16'b00000_000000_11111;

parameter [9:0] PIC_HPOS = 10'd0; // 图片左上角行坐标

parameter [9:0] PIC_VPOS = 10'd0; // 图片左上角列坐标

parameter [9:0] PIC_WIDTH = 10'd75; // 图片宽度

parameter [9:0] PIC_HEIGHT = 10'd105; // 图片高度

// 判断现在的坐标是不是图像的显示坐标

reg pic_area; // 指示当前坐标是否是图像显示区域

always @ (posedge clk_25MHz or negedge rst) begin

if(!rst)begin

pic_area = 1'b0;

end

else begin

if((pixel_hpos >= PIC_HPOS) && (pixel_hpos < PIC_HPOS + PIC_WIDTH) && (pixel_vpos >= PIC_VPOS) && (pixel_vpos < PIC_VPOS + PIC_HEIGHT))begin

pic_area <= 1'b1;

end

else begin

pic_area <= 1'b0;

end

end

end

// 例化RAM

reg [13:0]wraddr;

reg [12:0]rdaddr;

reg rden;

reg wren;

reg [7:0]wrdata;

wire [15:0]rddata;

RAM RAM1(

.data(wrdata),

.rdaddress(rdaddr),

.rdclock(clk_25MHz),

.rden(rden),

.wraddress(wraddr),

.wrclock(clk_25MHz),

.wren(wren),

.q(rddata)

);

// 确定RAM读使能信号

always@(posedge clk_25MHz or negedge rst)begin

if(!rst)begin

rden <= 1'b0;

end

else begin

rden <= 1'b1;

end

end

// 确定RAM读地址信号

always@(posedge clk_25MHz or negedge rst)begin

if(!rst)begin

rdaddr <= 13'd0;

end

else begin

if(pic_area == 1'b1)begin

rdaddr <=(pixel_hpos - PIC_HPOS) + ((pixel_vpos == 10'd0)?16'd0:((pixel_vpos - 1'b1)* PIC_WIDTH));

end

end

end

// 确定RAM写端口的各信号

always@(*)begin

wraddr <= 14'd0;

wren <= 1'b0;

wrdata <= 8'd0;

end

// 判断当前坐标的像素数据

always @ (posedge clk_25MHz or negedge rst)begin

if(!rst)begin

pixel_data <= 16'd0;

end

else begin

if(pic_area == 1'b0)begin

if (pixel_hpos >= 0 && pixel_hpos <= (H_DISP / 5) * 1)

pixel_data <= WHITE;

else if (pixel_hpos >= (H_DISP / 5) * 1 && pixel_hpos < (H_DISP / 5) * 2)

pixel_data <= BLACK;

else if (pixel_hpos >= (H_DISP / 5) * 2 && pixel_hpos < (H_DISP / 5) * 3)

pixel_data <= RED;

else if (pixel_hpos >=(H_DISP / 5) * 3 && pixel_hpos < (H_DISP / 5) * 4)

pixel_data <= GREEN;

else

pixel_data <= BLUE;

end

else begin

pixel_data <= rddata;

end

end

end

endmodule

testbench代码也附上

`timescale 1ns/1ps

module top_tb ();

reg clk_48MHz;

reg rst;

wire vga_hs; // 输出到vga接口的行同步信号

wire vga_vs; // 输出到vga接口的场同步信号

wire [15:0] vga_rgb; // 输出到vga接口的像素数据

initial begin

clk_48MHz = 1'b0;

forever #10.4 clk_48MHz = ~clk_48MHz;

end

initial begin

rst = 1'b0;

#50 rst = 1'b1;

//#1000 $stop;

end

vga_colorbar_top vga_colorbar_top1(

.sys_clk(clk_48MHz),

.sys_rst(rst),

.vga_hs(vga_hs),

.vga_vs(vga_vs),

.vga_rgb(vga_rgb)

);

endmodule

还剩下生成25MHZ时钟的模块和RAM模块,这里均使用ip核实现,下文会介绍如何使用

实现结果:

这里图片没有正确显示的原因是我的FPGA资源不够,导致RAM的深度不够,不能存储图片的所有数据,下文会详细解释

IP核使用:

双口RAM设置PLL设置(有很多文章详细的介绍了,这里不再赘述)

双口RAM设置:

打开quartus软件,点击tool>MegaWizard Plug-In Manager,在弹出的界面直接点击Next

如下图,选择RAM:2-PORT,再点击三个小点选择文件保存路径,笔者建议再项目文件夹中新建一个名叫IP的文件夹专门存放IP核文件

如下图,注意,选择路径的时候需要在最后写上生成的IP的名字,然后点击Next

如下图操作,点击next

注意,要根据自己板子的资源选择合适的深度,否则资源不够会报错

这里根据自己的需求选择

这里直接点击next

这里我们选择初始化数据,点击Browse选择数据文件,这个数据文件格式可以是.HEX或者.mif,下文会介绍如何使用我们自己的图片生成该文件

这里有一个坑要说明!!

我这里选择.hex文件的路径是C:/Users/Administrator/Desktop/PIC24.hex并没有将该文件保存在我的项目文件夹里,是因为如果保存在项目文件夹里,这里的路径会变得很长,这时候如果使用modelsim仿真,会发现读出的数据全为0000,所以这里的文件路径要尽可能地短,这里我花了好长时间才找到原因!太坑了!

`

后面一路next就行了,这样我们就生成了我们的RAM IP核

IP核生成之后,也可以进行修改,并且前面提到的mif文件路径过长的问题,可以如下图解决

生成hex/mif文件:

选择一副我们的图片,修改分辨率为合适的大小,这里推荐使用windows自带的画图工具

点击上方的重新调整大小,勾选保持纵横比,选择合适的尺寸

这里非常值得注意!

VGA显示图像是RGB565,红色-绿色-蓝色分别分配5bit-6bit-5bit数据,一个像素就是16bit数据我们的RAM每个地址保存8bit数据,深度我们选择的是16384(=2 ^ 14)。我们选择读出时数据是16bit,写入时数据时8bit,所以读出地址深度为8192 = 16384/2 (=2 ^ 13),也就是说我们的RAM最多能保存8192个像素数据,所以我们选择的图像像素不能超过8192上面我放出的我的是实验结果就是因为我选择的图片分辨率为100*140=14000,大于8192,导致显示的图像后半部分重复了图像开头的数据

点击确定后,另存为图像,保存成bmp格式

百度搜索一下这个软件,随便一搜就能搜出来

打开BMP2MIF软件,点击加载,右侧可以看到图像的信息,如下图操作,点击生成

生成的mif或hex文件我们就可以在生成IP核的时候用上了

这里也有一个坑!

前面说过图像像素不能超过8192,所以我一开始是选择的分辨率75 * 100,然而这个软件生成的mif文件我在生成IP核之后,图片显示出错,成了下图的样子,我猜测应该是分辨率太低,软件生成了错误的mif文件,在把图像分辨率调成100*140之后显示就正常了,这里也让我花了好长时间!

ModelSim使用简单介绍:

首先,点击tool>options,在弹出的窗口如下图所示,确认路径是否正确,如果没有路径就需要手动添加(也有可能是因为你没有下载ModelSim,需要额外下载)

注意:ModelSim 的路径是modelsim_ae,而ModelSim-Altera的路径是modelsim_ase,不要弄反了,很容易有人弄反(比如我)

点击assignment>setting,弹出的窗口按如下图操作

设置好了testbench文件之后,点击tool>run simulation tool > RTL simulation,弹出如下界面

点击左边的模块名,右侧显示所有变量名,拖动就可以放进仿真列表里

一些经常用到的功能

结论:

总的来说没什么难点,就是因为不熟悉,导致有一些坑没注意到,调试找原因还是很累人的

相关推荐

浩方上有哪些游戏
mobile365bet365com

浩方上有哪些游戏

📅 08-24 👁️ 9823
面子值多少钱?宋仁宗告诉你
365平台被黑

面子值多少钱?宋仁宗告诉你

📅 09-03 👁️ 7752
矢量跑酷Vector的入坑指南、下载更新方法、常见问题等