基于Vivado与VGA接口的FPGA五子棋:从时序驱动到胜负判定的全流程解析

张开发
2026/4/10 15:20:20 15 分钟阅读

分享文章

基于Vivado与VGA接口的FPGA五子棋:从时序驱动到胜负判定的全流程解析
1. 从零搭建FPGA五子棋的硬件框架第一次接触FPGA开发板时看着密密麻麻的引脚和芯片确实有点发怵。但实际用起来你会发现只要掌握几个核心模块整个系统就能跑起来。我用的这块xc7a35tcsg324-1开发板算是入门级FPGA里性价比很高的选择。板子自带50MHz晶振通过PLL倍频就能得到我们需要的各种时钟信号。VGA接口看起来复杂其实就5根关键线RGB三色信号、行同步和场同步。在硬件连接时要注意开发板上的3.3V电平需要用电阻分压网络转换成VGA标准的0.7V模拟信号。我当初偷懒直接接了杜邦线结果画面出现重影后来用330欧和470欧电阻做了分压才解决。Vivado 2019.2的环境配置有个坑要注意安装时必须勾选System Generator组件否则后面做仿真时会报找不到simulator的错误。新建工程时器件型号一定要选对我刚开始选成了xc7a35tcpg236-1结果生成比特流时一直报错排查了半天才发现型号尾缀不对。2. VGA显示驱动的精妙设计VGA的时序控制就像指挥交通的交警。以640x48060Hz模式为例每秒钟要完成60帧画面的刷新每帧又分为525行480行可见45行消隐。这就像把一栋大楼分成525层每层有800个房间640可见160消隐我们需要精确控制电子枪的扫描节奏。parameter hsync_end 10d95, hdata_begin 10d143, hdata_end 10d783, hpixel_end 10d799, vsync_end 10d1, vdata_begin 10d34, vdata_end 10d514, vline_end 10d524;这段参数定义了VGA的时空坐标体系。实际调试时我用SignalTap抓取hsync和vsync信号发现场同步的前肩front porch比标准时序多了3个时钟周期导致图像右偏。后来调整了hdata_begin的取值才解决。这里有个技巧可以先用VGA测试图案生成器验证时序是否正确再叠加自己的图像逻辑。时钟分频是另一个容易出错的地方。系统时钟100MHz通过二分频得到50MHz再二分频得到25MHz的VGA像素时钟。我最初用计数器实现分频结果出现毛刺后来改用PLL才稳定。关键代码如下always(posedge clk) begin if(cnt_clk 1) begin vga_clk ~vga_clk; cnt_clk 0; end else cnt_clk cnt_clk 1; end3. 棋盘渲染与交互逻辑实现画棋盘就像在Excel里画表格。我定义了10x10的网格每个格子40x40像素。用两个always块分别生成横线和竖线// 竖线生成 always(posedge vga_clk) begin if(hcount200||hcount640) v_data 12h000; else if(hcount 240||hcount 280||...|hcount600) v_data 12h000; else v_data 12hfff; end // 横线生成 always(posedge vga_clk) begin if(vcount50||vcount490) h_data 12h000; else if(vcount 90||vcount 130||...|vcount450) h_data 12h000; else h_data 12hfff; end棋子移动采用光标确认的方式。用ball_x_pos和ball_y_pos记录光标位置通过按键改变这两个值。这里有个用户体验优化点给移动加上边界检测当光标到达棋盘边缘时自动跳到对侧就像吃豆人游戏穿墙效果always(posedge clk_50ms) begin case(yidong[4:0]) 5b00100: // 左移 if (ball_x_pos 10d240) ball_x_pos10d600; else ball_x_posball_x_pos-10d40; // 其他方向同理 endcase end落子逻辑用二维数组R记录每个格子的占用状态数组U记录棋子归属。按下确认键时先检查该位置是否已有棋子R[d][c]0再更新状态if(R[d][c]0) begin R[d][c]1; U[d*10c]flag[0]; // flag区分玩家 end4. 胜负判定的状态机设计五子棋的胜负判断就像玩连连看要检查横、竖、斜四个方向是否有五子连珠。我最初的做法是实时扫描整个棋盘结果导致时序违例。后来优化为只检查最新落子点周围的可能连线// 横向检测 if(b5 U[b*10a]1 U[b*10a1]1 U[b*10a2]1 U[b*10a3]1 U[b*10a4]1) over11; // 竖向检测 else if(a5 U[b*10a]1 U[b*10a10]1 U[b*10a20]1 U[b*10a30]1 U[b*10a40]1) over11; // 斜向检测左上到右下 else if(a5 b5 U[b*10a]1 U[b*10a11]1 U[b*10a22]1 U[b*10a33]1 U[b*10a44]1) over11; // 斜向检测右上到左下 else if(b5 a4 U[b*10a]1 U[b*10a9]1 U[b*10a18]1 U[b*10a27]1 U[b*10a36]1) over11;胜利提示的显示也很有讲究。我设计了两种风格的R WIN和G WIN图案用多层if-else实现字符点阵always(posedge vga_clk) begin if(((hcount220hcount240)||...||(hcount640hcount660)) (vcount 200vcount 360)) z1_data 12hf00; // 红色线段 else if(hcount340hcount460vcount340vcount 360) z1_data 12hf00; // W字母横笔 // 其他笔画判断... else z1_data 12hfff; end调试这个模块时遇到个有趣的问题当同时满足多个胜利条件时over1和over2信号会出现竞争。后来加了优先级编码才解决。这也提醒我们硬件设计中的条件判断要特别注意互斥性。

更多文章