1. 项目概述从点灯到显示FPGA学习的必经之路玩过FPGA的朋友都知道第一个项目通常是“点灯”。当LED在你的指尖下明灭闪烁时那种亲手赋予硬件生命的成就感是无与伦比的。但点灯之后呢很多人的学习路径就卡住了面对复杂的时序和接口不知从何下手。在我看来“VGA显示”就是那个完美的“下一步”。它不像高速Serdes或PCIe那样令人望而生畏又远比简单的GPIO控制要复杂和有趣得多。通过驱动一个古老的VGA接口在屏幕上画出图形你实际上是在亲手构建一个微型的“显卡”这能让你深刻理解数字系统中最核心的概念之一——时序。VGAVideo Graphics Array是一个诞生于1987年的模拟视频接口标准虽然如今在消费电子领域已被HDMI、DP等数字接口取代但在工业控制、嵌入式显示、FPGA教学等领域它依然因其简单、直观、易于理解的时序模型而备受青睐。对于FPGA初学者而言实现VGA驱动是一个综合性极强的练手项目你需要设计状态机来严格遵循视频时序需要操作双端口RAM或FIFO来缓存图像数据需要理解像素时钟、行同步、场同步之间的关系还需要用Verilog或VHDL编写清晰可维护的代码。这几乎涵盖了数字逻辑设计的半壁江山。这个项目适合已经掌握FPGA开发流程如使用Vivado、Quartus等工具完成综合、实现、下载、熟悉硬件描述语言基本语法、并成功点过灯的初学者。如果你对“如何在屏幕上显示一个方块、一行文字甚至一幅图片”感到好奇那么跟着我一步步拆解VGA显示的奥秘将会是你FPGA学习路上一次质的飞跃。我们不止要“实现”功能更要弄懂每一个参数背后的“为什么”以及在实际操作中那些教程里不会写的“坑”。2. VGA显示的核心原理与接口拆解2.1 模拟信号时代的遗产VGA接口引脚与时序很多人一看到VGA那个15针的D-Sub接口就觉得复杂其实它的核心信号只有5个。我们拆开来看RGB模拟信号Pin 1, 2, 3这是红、绿、蓝三原色的模拟电压信号。电压范围通常是0~0.7V电压越高对应颜色的亮度越强。FPGA是数字器件输出的是0和1这样的数字电平所以我们需要通过电阻分压网络常称为“电阻DAC”将数字信号转换为模拟电压。例如如果我们希望输出8位色深256色的红色那么就需要8根FPGA IO线通过一个由8个精密电阻构成的分压网络来产生256个不同的电压等级。这是VGA驱动在硬件上第一个需要动手的地方。行同步HSync, Pin 13与场同步VSync, Pin 14这是两个数字信号负极性脉冲有效常见于640x48060Hz模式。它们的作用是告诉显示器每一行HSync和每一帧VSync的开始位置。这两个信号是纯粹的时序信号FPGA可以直接用IO口驱动。地线Pin 5, 6, 7, 8, 10模拟地和数字地良好的共地是保证图像稳定不闪烁的关键。理解了信号我们再来看最核心的时序图。VGA的时序可以分解为行时序和场时序两者结构类似都包含四个部分有效显示区域Active Video在这个时间段内FPGA需要持续输出当前像素点的RGB数据。对于640x480分辨率就是每行输出640个像素点每场输出480行。行/场同步脉冲Sync Pulse发出一个负脉冲指示新的一行或新的一帧开始。脉冲的宽度有严格规定。后沿Back Porch同步脉冲结束到有效显示区域开始之间的时间。可以理解为“消隐期”的一部分。前沿Front Porch有效显示区域结束到下一个同步脉冲开始之间的时间。同样是消隐期。整个屏幕的刷新就是由HSync控制从左到右画完一行由VSync控制从上到下画完480行如此循环。所有的参数如脉冲宽度、前后沿时间都基于一个基准时钟——像素时钟Pixel Clock。例如640x48060Hz的标准像素时钟是25.175MHz。这意味着FPGA必须生成一个25.175MHz的时钟每个时钟周期输出一个像素点的数据。注意很多教程和开发板为了简化直接使用25MHz或27MHz等接近的时钟这在大部分显示器上也能工作但严格来说不是标准时序可能在部分挑剔的显示器上出现画面抖动或无法识别。在项目初期我们可以用近似时钟快速验证但深入理解时应该学会使用FPGA内部的PLL或MMCM资源来精确生成25.175MHz时钟。2.2 FPGA的角色一个定制化的视频时序发生器在VGA显示系统中FPGA扮演着**视频时序发生器Timing Generator和图像数据源Frame Buffer Controller**的双重角色。首先FPGA需要根据所选分辨率如640x480的时序标准用硬件描述语言编写一个时序生成模块。这个模块的核心是两个计数器行计数器H_Cnt和场计数器V_Cnt。它们由像素时钟驱动不断累加。然后我们根据计数器的值来产生HSync和VSync信号并生成两个重要的标志信号h_valid行有效和v_valid场有效。当h_valid和v_valid同时为高时就表示当前处于有效显示区域需要输出像素数据。其次FPGA需要管理图像数据。最简单的“显示”是一个纯色背景。我们只需要在有效显示区域内让RGB输出固定的数字值即可。比如输出8‘hFF, 8’h00, 8‘h00就是满亮的红色。但要想显示图案、文字或动画就需要一个帧缓冲区Frame Buffer。通常我们会使用FPGA内部的Block RAM资源构建一个双端口RAM。一个端口由时序生成逻辑按顺序读取读地址由H_Cnt和V_Cnt计算得出另一个端口则由你的图像更新逻辑如CPU、状态机来写入新的图像数据。这就是一个最简化的显示系统架构。2.3 从理论到板卡硬件连接方案在动手写代码前我们必须搞定硬件连接。你需要一块带有VGA接口的FPGA开发板或者自己用FPGA核心板搭配一个VGA转换模块。方案一使用开发板上的VGA接口大多数教学用FPGA开发板如Digilent的Nexys系列、黑金等的AX系列都集成了VGA接口。板上通常已经做好了电阻分压网络。你只需要在约束文件XDC或QSF中将Verilog代码中的hsync、vsync、red[7:0]等信号名绑定到对应的FPGA引脚号上即可。这是最省事的方式。方案二自制VGA输出模块如果你的板子没有VGA口可以自制一个简单的转换模块。核心就是三个电阻分压网络。对于最简单的1位色黑白显示每色用一个电阻即可如330欧姆电阻接到VGA接口另一端接FPGA IO。对于更丰富的颜色就需要更复杂的网络。例如一种常见的“3位RGB”方案8种颜色R、G、B各用1位FPGA IO口控制。每个IO口通过一个电阻如470欧姆连接到VGA接口的对应引脚。VGA接口的RGB引脚还需要通过一个75欧姆的电阻下拉到地以匹配传输线特性阻抗很多显示器内部已有外部可不接。更高级的可以用R-2R梯形电阻网络来实现多位DAC但电路复杂初学者建议从1位色或使用现成模块开始。实操心得在连接硬件时务必确保共地良好。FPGA的GND必须与VGA接口的GND多个引脚可靠连接。图像出现重影、抖动很多时候不是代码问题而是地线噪声或信号完整性不好。使用带屏蔽的VGA线缆也能有效改善。3. 核心模块设计与代码实现详解3.1 时序生成模块状态机与计数器的艺术时序生成模块是VGA驱动的“心脏”。我们不推荐使用一堆if-else语句来拼接时序那样代码难以维护和移植。更优雅的方式是使用状态机FSM的思想或者直接用清晰的计数器逻辑。下面以Verilog为例展示一个640x48060Hz的时序生成模块的关键部分。我们首先定义所有时序参数单位像素时钟周期。这些参数可以从VESA标准时序表中查到。module vga_timing ( input wire clk_25m, // 25MHz像素时钟近似值 input wire rst_n, output reg hsync, output reg vsync, output wire [10:0] pixel_x, // 当前像素横坐标 (0~639) output wire [10:0] pixel_y, // 当前像素纵坐标 (0~479) output wire data_valid // 有效显示区域标志 ); // 640x48060Hz 时序参数 (像素数) parameter H_DISP 640; parameter H_FRONT 16; parameter H_SYNC 96; parameter H_BACK 48; parameter H_TOTAL H_DISP H_FRONT H_SYNC H_BACK; // 800 parameter V_DISP 480; parameter V_FRONT 10; parameter V_SYNC 2; parameter V_BACK 33; parameter V_TOTAL V_DISP V_FRONT V_SYNC V_BACK; // 525 reg [10:0] h_cnt; // 行计数器 (0~799) reg [10:0] v_cnt; // 场计数器 (0~524) // 行计数器逻辑 always (posedge clk_25m or negedge rst_n) begin if (!rst_n) h_cnt 11d0; else if (h_cnt H_TOTAL - 1) h_cnt 11d0; else h_cnt h_cnt 1b1; end // 场计数器逻辑当一行结束时h_cnt循环归零场计数器加一 always (posedge clk_25m or negedge rst_n) begin if (!rst_n) v_cnt 11d0; else if (h_cnt H_TOTAL - 1) begin if (v_cnt V_TOTAL - 1) v_cnt 11d0; else v_cnt v_cnt 1b1; end end // 生成行同步信号 (负极性) always (posedge clk_25m or negedge rst_n) begin if (!rst_n) hsync 1b1; // 默认高电平无效 else if (h_cnt H_DISP H_FRONT h_cnt H_DISP H_FRONT H_SYNC) hsync 1b0; // 同步脉冲期间拉低 else hsync 1b1; end // 生成场同步信号 (负极性) always (posedge clk_25m or negedge rst_n) begin if (!rst_n) vsync 1b1; else if (v_cnt V_DISP V_FRONT v_cnt V_DISP V_FRONT V_SYNC) vsync 1b0; else vsync 1b1; end // 计算当前有效像素坐标 assign pixel_x (h_cnt H_DISP) ? h_cnt : H_DISP - 1; assign pixel_y (v_cnt V_DISP) ? v_cnt : V_DISP - 1; // 生成数据有效信号 assign data_valid (h_cnt H_DISP) (v_cnt V_DISP); endmodule代码解析与关键点计数器设计h_cnt和v_cnt是核心。h_cnt每个时钟加1计满一行800个周期后归零同时触发v_cnt加1。v_cnt计满一场525行后归零。这就构成了扫描的基础。同步信号生成根据计数器值在特定的区间内将hsync或vsync拉低产生脉冲。注意区间判断使用了和这是为了避免出现毛刺。坐标与有效信号pixel_x和pixel_y只在有效显示区域内递增方便后续图像生成模块使用。data_valid信号是后续模块的“使能”信号非常重要。参数化所有时序参数都用parameter定义在模块开头。如果你想改为800x600的分辨率只需要修改这些参数即可无需改动核心逻辑这是良好设计习惯的体现。3.2 图像数据生成从色块到位图有了时序我们就可以在有效区域内“作画”了。最简单的图像生成模块就是一个组合逻辑根据pixel_x和pixel_y来输出RGB值。例1显示一个彩色条纹背景module image_gen ( input wire [10:0] pixel_x, input wire [10:0] pixel_y, output reg [7:0] vga_r, output reg [7:0] vga_g, output reg [7:0] vga_b ); always (*) begin // 根据横坐标分区显示不同颜色竖条纹 if (pixel_x 213) begin vga_r 8hFF; vga_g 8h00; vga_b 8h00; // 红色区域 end else if (pixel_x 426) begin vga_r 8h00; vga_g 8hFF; vga_b 8h00; // 绿色区域 end else begin vga_r 8h00; vga_g 8h00; vga_b 8hFF; // 蓝色区域 end end endmodule将image_gen模块的pixel_x、pixel_y连接到vga_timing模块的输出将vga_r/g/b输出到顶层的VGA接口。上板后你应该能看到屏幕被分成红、绿、蓝三块。例2显示一个实心方块要显示一个方块就需要判断坐标是否在一个矩形区域内。// 在image_gen模块的always块中添加 reg square_area; square_area (pixel_x 200 pixel_x 300) (pixel_y 150 pixel_y 250); if (square_area) begin vga_r 8hFF; vga_g 8hFF; vga_b 8h00; // 方块内为黄色 end else begin // 方块外的颜色比如黑色背景 vga_r 8h00; vga_g 8h00; vga_b 8h00; end例3连接帧缓冲区Block RAM显示复杂图像要显示一幅预存的图片如LOGO我们需要将图片数据通常由MATLAB或Python脚本转换成的.coe或.mif文件预先写入FPGA的Block RAM中。图像生成模块则根据pixel_x和pixel_y计算ROM的读地址将读出的数据赋给RGB输出。module image_gen_from_rom ( input wire clk, input wire [10:0] pixel_x, input wire [10:0] pixel_y, input wire data_valid, output reg [7:0] vga_r, output reg [7:0] vga_g, output reg [7:0] vga_b ); wire [15:0] rom_addr; // ROM地址取决于图片大小 wire [23:0] rom_data; // ROM数据假设为24位RGB888格式 // 计算ROM地址addr y * IMAGE_WIDTH x // 注意如果图片尺寸小于屏幕需要做居中计算 localparam IMG_WIDTH 100; localparam IMG_HEIGHT 100; wire [10:0] rel_x pixel_x - 270; // 假设居中显示 (640-100)/2270 wire [10:0] rel_y pixel_y - 190; // (480-100)/2190 assign rom_addr (data_valid rel_x IMG_WIDTH rel_y IMG_HEIGHT) ? (rel_y * IMG_WIDTH rel_x) : 0; // 实例化Block RAM IP核存储图片数据 blk_mem_gen_0 your_image_rom ( .clka(clk), .addra(rom_addr), .douta(rom_data) ); // 在时钟驱动下输出RGB数据保证时序对齐 always (posedge clk) begin if (data_valid rel_x IMG_WIDTH rel_y IMG_HEIGHT) begin vga_r rom_data[23:16]; vga_g rom_data[15:8]; vga_b rom_data[7:0]; end else begin // 非图片区域显示背景色如黑色 vga_r 8h00; vga_g 8h00; vga_b 8h00; end end endmodule这里的关键是地址计算和时序对齐。ROM读取通常有一个时钟周期的延迟所以RGB输出也需要打一拍确保与data_valid信号同步。3.3 顶层集成与时钟管理最后我们需要一个顶层模块top将时序生成、图像生成模块例化连接并处理好时钟。module vga_top ( input wire sys_clk, // 系统时钟如50MHz input wire rst_n, output wire vga_hsync, output wire vga_vsync, output wire [7:0] vga_r, output wire [7:0] vga_g, output wire [7:0] vga_b ); wire clk_25m; // VGA像素时钟 wire locked; // PLL锁定信号 wire rst; // 系统复位信号 // 复位信号处理外部按键复位或PLL未锁定时系统处于复位状态 assign rst ~rst_n | ~locked; // 使用PLL将50MHz系统时钟转换为25MHz像素时钟 clk_wiz_0 your_pll_inst ( .clk_in1(sys_clk), .clk_out1(clk_25m), .locked(locked) ); // 时序生成模块 wire [10:0] pixel_x, pixel_y; wire data_valid; vga_timing u_vga_timing( .clk_25m(clk_25m), .rst_n(~rst), .hsync(vga_hsync), .vsync(vga_vsync), .pixel_x(pixel_x), .pixel_y(pixel_y), .data_valid(data_valid) ); // 图像生成模块 image_gen_from_rom u_image_gen( .clk(clk_25m), .pixel_x(pixel_x), .pixel_y(pixel_y), .data_valid(data_valid), .vga_r(vga_r), .vga_g(vga_g), .vga_b(vga_b) ); endmodule时钟管理的重要性我强烈建议使用FPGA内部的时钟管理单元Xilinx叫MMCM/PLLIntel叫PLL来生成像素时钟。原因有三第一可以精确生成25.175MHz这样的非整数频率第二可以生成相位对齐、低抖动的时钟提升图像稳定性第三可以输出一个locked信号作为系统内部复位的条件之一避免在时钟未稳定时逻辑紊乱。4. 工程实现、调试与深度优化4.1 开发环境搭建与工程创建无论你使用Xilinx的Vivado还是Intel的Quartus流程大同小异。这里以Vivado为例简述关键步骤创建工程选择正确的FPGA器件型号务必与你的开发板一致。添加设计源文件将上面编写的vga_timing.v、image_gen.v、vga_top.v等文件添加到工程中。添加IP核通过IP Catalog添加Clock Wizard生成像素时钟和Block Memory Generator存储图像。配置Block RAM时选择“Single Port ROM”加载由图片转换来的.coe文件。编写约束文件.xdc这是连接逻辑与物理引脚的关键。你需要根据开发板原理图找到VGA接口对应的FPGA引脚号并设置正确的IO标准通常是LVCMOS33。# 示例Nexys4 DDR开发板的VGA接口约束 set_property PACKAGE_PIN G19 [get_ports {vga_r[7]}] set_property IOSTANDARD LVCMOS33 [get_ports {vga_r[7]}] # ... 为vga_r[6:0], vga_g[7:0], vga_b[7:0], vga_hsync, vga_vsync 重复上述操作 set_property PACKAGE_PIN B11 [get_ports vga_hsync] set_property IOSTANDARD LVCMOS33 [get_ports vga_hsync]综合、实现、生成比特流点击“Run Synthesis”和“Run Implementation”最后“Generate Bitstream”。下载与调试连接开发板通过JTAG口下载比特流文件。如果使用Vivado可以直接用“Hardware Manager”中的“ILA集成逻辑分析仪”来抓取hsync、vsync、data_valid等内部信号波形这是最强大的调试手段。4.2 典型问题排查与实战技巧即使代码看似正确第一次上电也常常看不到预期图像。别慌按以下步骤排查问题1屏幕显示“无信号”或进入节能模式。排查思路这说明显示器根本没有检测到有效的同步信号。这是最可能出问题的地方。检查步骤硬件连接首先确认VGA线是否插紧开发板是否供电。用万用表测量hsync和vsync引脚对地是否有电压变化。约束文件检查约束文件中hsync和vsync的引脚号是否绑定正确。一个常见的低级错误是信号名与约束文件中的端口名不匹配大小写、位宽。时序参数确认你使用的时序参数特别是H_SYNC和V_SYNC的脉冲宽度对于你的显示器是否兼容。有些显示器对非标准时序容忍度较低。可以尝试使用绝对标准的640x48060Hz参数。极性确认hsync和vsync的极性。虽然640x48060Hz通常是负极性但也有例外。可以尝试在代码中将极性反转试试即同步期间输出高电平。使用ILA抓波形这是终极手段。在Vivado中将hsync、vsync、h_cnt、v_cnt添加到ILA核中下载后触发抓取。观察波形看同步脉冲的周期、宽度是否与设计一致。如果计数器不计数检查复位逻辑和时钟是否真的连通了。问题2屏幕有显示但图像错位、滚动或撕裂。排查思路这通常是时序参数中的前后沿Porch设置不对或者像素时钟不准导致的。检查步骤核对时序参数逐字核对H_FRONT,H_SYNC,H_BACK,H_TOTAL等所有参数一个数字错误就会导致整屏偏移。建议将参数与官方VESA标准文档对照。检查像素时钟如果你用的是近似时钟如25MHz代替25.175MHz虽然大部分显示器能自适应但敏感的显示器可能会出现轻微滚动。使用PLL精确生成时钟可以解决此问题。在ILA中测量实际生成的时钟频率。检查消隐区数据确保在非data_valid区域即前后沿和同步脉冲期间你的RGB输出是固定的值通常是黑色。如果这些区域数据不稳定可能会干扰显示器的锁相环。问题3能显示色块但显示图片时花屏、错色。排查思路问题集中在图像数据通路和帧缓冲区。检查步骤ROM数据与地址检查图片转换脚本生成的.coe文件格式是否正确如二进制、十六进制。检查ROM的位宽、深度配置是否与图片数据匹配。在ILA中抓取rom_addr和rom_data看地址是否按预期递增读出的数据是否与原始图片数据一致。数据对齐确保从ROM读出数据到赋值给vga_r/g/b的流水线延迟是固定的。如果data_valid、像素坐标和RGB数据之间没有对齐就会显示错位。通常的做法是让所有信号都使用同一个像素时钟clk_25m进行寄存器打拍输出。Block RAM的初始值确认Block RAM IP核是否成功加载了.coe文件。可以在Vivado中打开“Block Memory Generator”IP的配置界面查看“Memory Initialization”部分是否有你的文件。实操心得ILA是FPGA开发的“眼睛”。很多时序问题靠仿真很难完全覆盖因为仿真模型是理想的。ILA可以让你看到信号在真实芯片中运行的波形。学会熟练添加ILA探针、设置触发条件、观察波形是FPGA调试能力提升的关键一步。不要嫌麻烦在关键路径如计数器、状态机、数据接口上挂上ILA能节省你大量瞎猜的时间。4.3 性能优化与功能扩展当基础显示稳定后你可以尝试以下进阶玩法这会让你的项目从“实验”升级为“作品”1. 实现多分辨率支持不要将时序参数硬编码在模块里。可以设计一个可配置的时序模块通过输入参数如resolution_sel来选择不同的参数集如640x480, 800x600, 1024x768。这需要你管理不同的像素时钟通常通过动态重配置PLL或使用多个时钟域来实现。2. 实现动态图形与动画让方块移动起来这需要你有一个图形渲染引擎。例如你可以定义一个方块的位置寄存器box_x,box_y在每个场同步VSync的上升沿更新它们的位置。然后在image_gen模块中判断当前像素是否在(box_x, box_y)定义的矩形内来决定像素颜色。这就是最简单的精灵Sprite渲染。更复杂的可以涉及直线、圆形的绘制算法如Bresenham算法的硬件实现。3. 接入外部图像源VGA显示系统可以作为其他模块的“显示器”。例如接摄像头将OV7670等摄像头模块的像素数据通过FIFO缓冲后写入到作为帧缓冲的双端口RAM的另一端实现实时视频显示。接CPU通过Wishbone、AXI等总线让软核CPU如MicroBlaze、NIOS II能够向帧缓冲区写入数据从而在屏幕上实现图形用户界面GUI。接视频解码芯片驱动ADV7125等高速视频DAC芯片可以实现更高色彩深度和分辨率的输出。4. 文本模式显示这是非常实用的一步。在ROM中存储一个字符点阵库如8x16的ASCII字库。图像生成模块根据pixel_x和pixel_y先计算出属于屏幕第几行第几列的字符再根据该字符的ASCII码从字库ROM中查找对应的行数据最后根据像素在字符内的位置输出黑白或彩色。结合一个文本缓冲区Text BufferRAM就可以实现类似终端的字符输出了。5. 从VGA出发FPGA图像处理的学习路径成功驱动VGA显示就像是打开了FPGA图像处理世界的大门。它为你后续的学习奠定了坚实的时序和架构基础。基于这个项目你可以自然地延伸到以下几个更有挑战性和实用性的方向方向一基于摄像头的实时图像处理这是最经典的应用。当你能够稳定显示静态图像后下一步就是显示动态图像。选择一个像OV5640这样的摄像头模块学习其SCCB/I2C配置接口和像素数据流通常是通过DVP或MIPI接口。你需要编写一个摄像头驱动模块将采集到的图像数据写入SDRAM或DDR3这类大容量外部存储器中作为帧缓存然后再用VGA驱动模块从存储器中读取并显示。在此基础上你就可以在数据流中加入图像处理模块比如实现边缘检测使用Sobel算子、二值化、颜色识别、目标跟踪等算法。你会发现之前在软件如OpenCV里一句函数调用的事情在FPGA里需要精心设计流水线、考虑资源与时序的平衡挑战巨大成就感也巨大。方向二高级视频接口与协议VGA是模拟接口是入门台阶。现代数字显示接口如HDMI、DisplayPort才是主流。学习驱动HDMI接口你会接触到TMDS编码、差分信号、DDC/EDID通信等新知识。虽然硬件上可能需要一个HDMI编码芯片如ADV7511或FPGA的GTX收发器但核心的时序生成、帧缓冲管理思想与VGA一脉相承。理解VGA后再学HDMI会感觉很多概念是相通的只是协议层更复杂。方向三2D图形加速与GUI如果你对在FPGA上构建更复杂的图形界面感兴趣可以尝试设计一个简单的2D图形加速器。定义一些绘图指令如画线、画矩形、填充、位块传输Blit通过一个命令FIFO接收来自CPU的指令然后在硬件中高效执行将结果写入帧缓冲区。这需要你设计一个微型的指令集、一个命令解析状态机以及多个并行的绘图流水线。最终你可以在CPU上运行一个轻量级GUI库通过这个加速器来渲染界面极大提升图形性能。方向四与软核处理器协同工作将你的VGA显示模块封装成一个带有标准总线接口如AXI4-Lite或Avalon-MM的IP核。然后在FPGA中实例化一个软核CPU如Xilinx的MicroBlaze或Intel的NIOS II通过总线来配置你的VGA IP如设置分辨率、基地址并向帧缓冲区写入数据。这样你就可以用C语言编程在屏幕上绘制图形、显示文字实现一个完整的嵌入式图形系统。这打通了FPGA硬件逻辑与软件编程的界限是向SoPC片上可编程系统设计迈进的重要一步。回过头看VGA显示项目就像一把钥匙。它强迫你去理解时序、状态机、存储器、时钟域这些FPGA设计的核心概念。过程中踩过的每一个坑解决的每一个问题都会转化为你硬件设计能力的扎实积累。我建议你不要仅仅满足于让屏幕亮起来而是尝试去实现上面提到的一两个扩展功能。当你看到自己编写的硬件逻辑流畅地显示着动态图像或图形界面时你会真正体会到用硬件“思考”和解决问题的乐趣。这正是FPGA的魅力所在。