-
FPGA - 现场可编程门阵列
我的FPGA学习笔记-不断改进和完善中,欢迎留言批斗,但别说的太过分,我并没有那么坚强。

起因

项目的起因是在刷哔哩哔哩的时候看到了一个视频

【2023全国大学生FPGA创新设计大赛-国一】紫光同创-视频处理魔盒_哔哩哔哩_bilibili

当时对于我这个小白来说,羡慕的死死的~~~

当即就在我的日记本上记下了这个项目,立誓要Copy一个~,但实在是太厉害了,我计划一点一点的分模块来完成吧,毕竟我现在也刚开始学习FPGA才2个多月,慢慢来吧

展示

起始这个功能并不难实现,具体的实现思路都在这篇文章里面写的很清楚了,感兴趣的就可以点进去慢慢看。FPGA:VGA显示器驱动设计 - quincy的日记

如果很难看懂的话,可以看下我文章末尾的一个参考链接,FPGA就是他带我入门的。

这里只说一下注意的点吧:

  1. 首先这个程序是一个纯verilog程序实现的,当然这里用到了一个PLL锁相环,将我的50MHZ系统时钟进行分频,输出一个25MHZ的时钟频率。(为什么请看上面的那篇文章)
  2. 字模是使用的字模提取的软件来提取的16进制数据
  3. 注意字体显示的区域和你显示器的分辨率,这里是640*480的显示分辨率

代码

vga_colorb.v
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
vga_pic.v
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
vga_ctrl.v
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

参考链接

  1. 66-第二十九讲-VGA显示器驱动设计与验证(一)_哔哩哔哩_bilibili

本站由 John Doe 使用 Stellar 1.28.1 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本"页面"访问 次 | 👀总访问 次 | 🥷总访客