起因
项目的起因是在刷哔哩哔哩的时候看到了一个视频
【2023全国大学生FPGA创新设计大赛-国一】紫光同创-视频处理魔盒
当时对于我这个小白来说,羡慕的死死的~~~
当即就在我的日记本上记下了这个项目,立誓要Copy一个~,但实在是太厉害了,我计划一点一点的分模块来完成吧,毕竟我现在也刚开始学习FPGA才2个多月,慢慢来吧
代码我都会上传到我的github上,需要的话大家可以去自取(感觉有帮助的话,请点个Star哦~)
Github: zikwq/FPGA_prj: FPGA_prj (github.com)
展示
VGA显示器驱动--字符游走
起始这个功能并不难实现,具体的实现思路都在这篇文章里面写的很清楚了,感兴趣的就可以点进去慢慢看。FPGA:VGA显示器驱动设计 - quincy的日记
如果很难看懂的话,可以看下我文章末尾的一个参考链接,FPGA就是他带我入门的。
这里只说一下注意的点吧:
-
首先这个程序是一个纯verilog程序实现的,当然这里用到了一个PLL锁相环,将我的50MHZ系统时钟进行分频,输出一个25MHZ的时钟频率。(为什么请看上面的那篇文章)
-
字模是使用的字模提取的软件来提取的16进制数据
-
注意字体显示的区域和你显示器的分辨率,这里是640*480的显示分辨率
代码
module vga_colorb(
input wire clk,
input wire rstn,
output wire hsync,
output wire vsync,
output wire [7:0] vga_rgb
);
wire vga_clk ;
wire locked ;
wire rst_n ;
wire [9:0] pix_x ;
wire [9:0] pix_y ;
wire [7:0] pix_data;
assign rst_n = (rstn && locked);
// 实例化PLL
clk_gen clk_gen_inst
(
.areset ( ~rstn ),
.inclk0 ( clk ),
.c0 ( vga_clk ),
.locked ( locked )
);
// 实例化控制模块
vga_ctrl vga_ctrl_init(
.vga_clk (vga_clk) ,
.rstn (rst_n) ,
.pix_data (pix_data) , // 显示数据
.pix_x (pix_x ) , // x轴有效坐标位置
.pix_y (pix_y ) , // y轴有效坐标位置
.hsync (hsync ) , // 行同步信号
.vsync (vsync ) , // 场同步信号
.vga_rgb (vga_rgb)
);
vga_pic vga_pic_inst(
. vga_clk(vga_clk) ,
. rst_n (rst_n) ,
. pix_x (pix_x) ,
. pix_y (pix_y) ,
.pix_data(pix_data)
);
endmodule
module vga_pic(
input wire vga_clk ,//25MHZ
input wire rst_n,
input wire [9:0] pix_x,
input wire [9:0] pix_y,
output reg [7:0]pix_data
);
reg [255:0] char [63:0];
wire [9:0] char_x;
wire [9:0] char_y;
reg [9:0] CHAR_B_H, CHAR_B_V; // 字符需要显示的位置
parameter CHAR_W = 10'd256, // 字符的宽度
CHAR_H = 10'd64; // 字符的高度
parameter BLACK = 8'b00000000; // RGB332色彩格式
parameter RED = 8'b11100000; //
assign char_x = (((pix_x >= CHAR_B_H) && (pix_x < (CHAR_B_H + CHAR_W)))
&& ((pix_y >= CHAR_B_V) && (pix_y < (CHAR_B_V +CHAR_H))))
? (pix_x - CHAR_B_H) : 10'h3ff;
assign char_y = (((pix_x >= CHAR_B_H) && (pix_x < (CHAR_B_H + CHAR_W)))
&& ((pix_y >= CHAR_B_V) && (pix_y < (CHAR_B_V +CHAR_H))))
? (pix_y - CHAR_B_V) : 10'h3ff;
// 分频时钟,因为前面的程序用到了PLL锁相环,所以无法使用50MHZ的系统时钟进行分频,这里使用锁相环输出的25MHZ进行分频
reg [27:0] cnt_500;
reg clk_500;
always @(posedge vga_clk or negedge rst_n) begin
if(!rst_n) begin
cnt_500 <= 28'd0;
clk_500 <= 1'd0;
end else if(cnt_500 == 28'd200000) begin
cnt_500 <= 28'd0;
clk_500 <= ~clk_500;
end else begin
cnt_500 <= cnt_500 + 1'd1;
end
end
// 典型错误的运动程序,
// always @(posedge clk_500 or negedge rst_n) begin
// if(!rst_n) begin
// CHAR_B_H <= 10'd0;
// CHAR_B_V <= 10'd0;
// EN_work <= 1'd0;
// end else if(EN_work == 1'd0) begin
// CHAR_B_H <= CHAR_B_H + 1'd1;
// CHAR_B_V <= CHAR_B_V + 1'd1;
// end else if(EN_work == 1'd1) begin
// CHAR_B_H <=CHAR_B_H -1'd1;
// CHAR_B_V <=CHAR_B_V -1'd1;
// end else if((CHAR_B_H == 10'd382) || (CHAR_B_V == 10'd412)) begin
// EN_work <= 1'd1;
// end else if((CHAR_B_H == 10'd0) || (CHAR_B_V == 10'd0)) begin
// EN_work <= 1'd0;
// end
// end
reg [1:0] dir_h; // 0的时候右移,1的时候左边=移
reg [1:0] dir_v; // 0的时候上移,1的时候下移
always @(posedge clk_500 or negedge rst_n) begin
if(!rst_n) begin
CHAR_B_H <= 10'd0; // 初始化字符水平位置
CHAR_B_V <= 10'd0; // 初始化字符垂直位置
dir_h <= 2'b0; // 初始化水平方向
dir_v <= 2'b0; // 初始化垂直方向
end else begin
if (dir_h == 2'b0) begin // 向右运动
CHAR_B_H <= CHAR_B_H + 1'd1;
if (CHAR_B_H == 10'd382) begin
dir_h <= 2'b1; // 到达边界,改变运动方向
end
end else begin // 左移动
CHAR_B_H <= CHAR_B_H - 1'd1;
if (CHAR_B_H == 10'd0) begin
dir_h <= 2'b0; // 到达边界,改变运动方向
end
end
if (dir_v == 2'b0) begin // 向下运动
CHAR_B_V <= CHAR_B_V + 1'd1;
if (CHAR_B_V == 10'd412) begin
dir_v <= 2'b1; // 到达边界,改变运动方向
end
end else begin // 向上运动
CHAR_B_V <= CHAR_B_V - 1'd1;
if (CHAR_B_V == 10'd0) begin
dir_v <= 2'b0; // 到达边界,改变运动方向
end
end
end
end
// 使用字符取模软件,提取“创新科技”的16进制字模数据
always @ (posedge vga_clk)
begin
char[0] <= 256'h 0000000000000000000000000000000000000000000000000000000000000000;
char[1] <= 256'h 0000000000000000000000000000000000000000000000000000000000000000;
char[2] <= 256'h 0000000000000000000000000000000000000000000000000000000000000000;
char[3] <= 256'h 0000000000000000000000000000000000000000000000000000000000000000;
char[4] <= 256'h 000000000000000000003C000000000000000000070000000001C00040000000;
char[5] <= 256'h 000000000038000000001F00000000000000000003E000000000F00078000000;
char[6] <= 256'h 00003800003E000000000F00000000000000060003E000000000F8007E000000;
char[7] <= 256'h 00003C00001F000000000700007000000000078001E000000000F0003E000000;
char[8] <= 256'h 00003E00001F000000000300007C000000000FC001E000000000F0003C000000;
char[9] <= 256'h 00003E00000F00000000000000FE000000001F8001C000000000F0003C000000;
char[10] <= 256'h00003C00000F0000000000E001FC000000003E0001C000000000E0003C000000;
char[11] <= 256'h00003C00000F0000000007F807C000000000780001C000000000E0003C000000;
char[12] <= 256'h00007800000E000000003FE00F0000000000E00001C000000000E00038000000;
char[13] <= 256'h00007C00000E0000000FFE003C0000000003C001C1C000000000E00038000000;
char[14] <= 256'h0000F780000E0000000FC301F000000000077001F1C000000000E000387C0000;
char[15] <= 256'h0000F3E0000E000000000381E000000000187800F1C000000000E0003FFC0000;
char[16] <= 256'h0001E1F83C0E0000000003C1E00000000000700071C000000000E0007FF00000;
char[17] <= 256'h0001C0FC1E0E000000030380E00000000000700011C000000000E007FFC00000;
char[18] <= 256'h0003C07C1E0E000000038300E00000000000700001C000000000FF1FFE000000;
char[19] <= 256'h0003803E1E06000000038600E0000000000033C001C00000000FFE0FF8000000;
char[20] <= 256'h0007001C1E06000000018600E003E00000003FE001C0000000FFF00030000000;
char[21] <= 256'h000700000E06000000008FF8E03FF0000001FE0781C0000000FFE00030000000;
char[22] <= 256'h000E00000E06000000007FE0E3FFF000001FF003C1C000000000E00030000000;
char[23] <= 256'h001C00000E060000000FF800FFE0000003FF7001E1C000000000E00030000000;
char[24] <= 256'h003803000E0600000FFF7000E0E000001FF8F000E1C000000000E00030000000;
char[25] <= 256'h00300FC00E0600000FF07800E0F000000FE0F00041C000000000E20033800000;
char[26] <= 256'h006FFFE00E06000003003800E0F000000301F00001C0F8000000EC003FE00000;
char[27] <= 256'h00C7C1F00E07000000003860C0F000000003F80001DFFC000000F801FFE00000;
char[28] <= 256'h018701E00E07000000003FF1C07000000003FF0003FFFC000000F07FE3E00000;
char[29] <= 256'h030701C00E0700000000FF81C0700000000777807FF800000001E07F03C00000;
char[30] <= 256'h060701C01E07000001FFF801C0700000000E73BFFFC000000003E00003C00000;
char[31] <= 256'h080701C01E07000000FE3801C0700000001C707FE1C000000007E00003800000;
char[32] <= 256'h000701C01E07000000003801C0700000001C700C01C00000001EE03C03800000;
char[33] <= 256'h000701C01E07000000003F01C07000000038700001C00000007CE01E03800000;
char[34] <= 256'h00071FC01C07000000303BC1807000000070700001C0000001F8E00707000000;
char[35] <= 256'h00070F800C070000003039E38070000000E0700001C000000FF0E00387000000;
char[36] <= 256'h000707800C070000007038E38070000001C0700001C000000FC0E001C7000000;
char[37] <= 256'h000707800007000000703863807000000300700001C000000780E000EE000000;
char[38] <= 256'h000703000007000000F03807007000000600700001C000000300E0007E000000;
char[39] <= 256'h000702040007000000E03807007000000800700001C000000000E0003C000000;
char[40] <= 256'h000700060007000000E03806006000001000700001C000000000E0007E000000;
char[41] <= 256'h000700060007000000E3780E006000000000700001C000000000E000FF800000;
char[42] <= 256'h00030006000F000000C1F81C006000000000700001C000000000E001F7C00000;
char[43] <= 256'h0003800E020F00000000F818006000000000700001C000000000E007C3F00000;
char[44] <= 256'h0003C03E01FF000000007830006000000000F00001C00000001FE01F81FC0000;
char[45] <= 256'h0003FFFE00FF000000003060006000000000F00001C000000007E0FC00FF8000;
char[46] <= 256'h0001FFFE007F000000000000006000000000700001C000000003E3E0007FF000;
char[47] <= 256'h00007FF8007E000000000000006000000000600001C000000001C000003FFC00;
char[48] <= 256'h00000000003E000000000000006000000000600001C000000001C00000000000;
char[49] <= 256'h00000000003C000000000000006000000000200001C000000000800000000000;
char[50] <= 256'h0000000000180000000000000040000000000000018000000000000000000000;
char[51] <= 256'h0000000000000000000000000040000000000000008000000000000000000000;
char[52] <= 256'h0000000000000000000000000000000000000000008000000000000000000000;
char[53] <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
char[54] <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
char[55] <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
char[56] <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
char[57] <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
char[58] <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
char[59] <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
char[60] <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
char[61] <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
char[62] <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
char[63] <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
end
// 如果扫描到了要显示的像素,则赋值给红色,否则赋值给黑色
always@(posedge vga_clk or negedge rst_n)
if(rst_n == 1'b0)
pix_data <= BLACK;
else if((((pix_x >= (CHAR_B_H - 1'b1))
&& (pix_x < (CHAR_B_H + CHAR_W -1'b1)))
&& ((pix_y >= CHAR_B_V) && (pix_y < (CHAR_B_V + CHAR_H))))
&& (char[char_y][10'd255 - char_x] == 1'b1))
pix_data <= RED;
else
pix_data <= BLACK;
endmodule
module vga_ctrl(
input wire vga_clk ,
input wire rstn ,
input wire [15:0] pix_data , // 显示数据
output wire [9:0] pix_x , // x轴有效坐标位置
output wire [9:0] pix_y , // y轴有效坐标位置
output wire hsync , // 行同步信号
output wire vsync , // 场同步信号
output wire [7:0] vga_rgb
);
parameter H_SYNC = 10'd96 ,
H_BACK = 10'd40 ,
H_LEFT = 10'd8 ,
H_VALID = 10'd640 ,
H_RIGHT = 10'd8 ,
H_FRONT = 10'd8 ,
H_TOTAL = 10'd800 ;
parameter V_SYNC = 10'd2 ,
V_BACK = 10'd25 ,
V_TOP = 10'd8 ,
V_VALID = 10'd480 ,
V_BOTTOM= 10'd8 ,
V_FRONT = 10'd2 ,
V_TOTAL = 10'd525 ;
reg [9:0] cnt_h ;
reg [9:0] cnt_v ;
wire rgb_valid ;
wire pix_data_req ;
// cnt_h行计数器,计数范围0-799
always @(posedge vga_clk or negedge rstn) begin
if(!rstn)
cnt_h <= 10'd0;
else if (cnt_h == H_TOTAL-1'd1)
cnt_h <= 10'd0;
else
cnt_h <= cnt_h + 1'd1;
end
// cnt_v列计数器,计数范围0-524并且行计数器计数到799
always @(posedge vga_clk or negedge rstn) begin
if(!rstn)
cnt_v <= 10'd0;
else if ((cnt_h == H_TOTAL-1'd1) && (cnt_v == V_TOTAL - 1'd1))
cnt_v <= 10'd0;
else if(cnt_h == H_TOTAL-1'd1)
cnt_v <= cnt_v + 1'd1;
else
cnt_v <= cnt_v;
end
// 定义有效数据范围,
assign rgb_valid = ((cnt_h >= H_SYNC + H_BACK + H_LEFT)
&& (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID)
&& (cnt_v >= V_SYNC + V_BACK + V_TOP)
&& (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)) ? 1'd1 : 1'd0;
assign pix_data_req = ((cnt_h >= H_SYNC + H_BACK + H_LEFT - 1'd1)
&& (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID -1'd1)
&& (cnt_v >= V_SYNC + V_BACK + V_TOP)
&& (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)) ? 1'd1 : 1'd0;
assign pix_x = (pix_data_req == 1'd1) ? (cnt_h - (H_SYNC + H_BACK + H_LEFT - 1'd1)) : 10'h3ff;
assign pix_y = (pix_data_req == 1'd1) ? (cnt_v - (V_SYNC + V_BACK + V_TOP)) : 10'h3ff;
assign hsync = (cnt_h <= H_SYNC - 1'd1) ? 1'd1 : 1'd0;
assign vsync = (cnt_v <= V_SYNC - 1'd1) ? 1'd1 : 1'd0;
assign vga_rgb = (rgb_valid == 1'd1) ? pix_data : 8'h0000;
endmodule