Verilog编写VGA显示的小游戏
来源:互联网 发布:淘宝有信用卡套现的吗 编辑:程序博客网 时间:2024/05/16 07:35
引言
这是我的数字集成电路课程设计。实现的是一个我们都玩过的小游戏:一块移动挡板,一个飞来飞去的球,挡板需要把球挡住,没挡住就算输。
代码已上传github,地址是
https://github.com/Wujh1995/Verilog-VGA-game
- 开发平台:Xilinx ISE
- 开发语言:Verilog HDL
- 运行平台:Digilent Basys2开发板
模块描述:
- 顶层模块:SBgame.v
- VGA显示及控制模块:VGA_Display.v
- 数码管显示模块:seven_seg.v
- 数码管译码模块:seg_decoder.v
端口描述:
- SBgame.v
module SBgame( input mclk, //全局时钟,50MHz input rst, //复位信号,高电平有效 input to_left, //挡板左移,高电平有效 input to_right, //挡板右移,高电平有效 input [3:0] bar_move_speed, //挡板移动速度控制,为零表示静止 output HSync, //VGA行扫描信号 output [2:1] OutBlue, //蓝色 output [2:0] OutGreen, //绿色 output [2:0] OutRed, //红色 output VSync, //VGA场扫描信号 output [3:0] seg_select, //数码管位选信号 output [6:0] seg_LED //数码管数据信号,没有小数点 );
关于VGA的显示原理,可以参考
http://blog.csdn.net/u013793399/article/details/51319235
Basys2开发板的数码管有4个,但是位选信号只有一个,为了“同时”显示4位,必须以扫描的方式显示,就像数字电路实验那样。
- VGA_Display.v
module VGA_Dispay( input clk, //输入时钟,50MHz input to_left, //同上 input to_right, //同上 input [3:0] bar_move_speed, //同上 output reg hs, //同上 output reg vs, //同上 output reg [2:0] Red,//同上 output reg [2:0] Green,//同上 output reg [1:0] Blue,//同上 output reg lose //游戏失败信号,没能成功挡住球的话就会触发一个高电平脉冲,用于数码管计数 );
- seven_seg.v
module seven_seg( input clk, //这些端口上面都已经讲过一次了 input rst, //就没什么好讲的了 input lose, output reg [3:0] select, output reg [6:0] seg );
模块实现
- 生成VGA扫描信号
详细原理请看
http://blog.csdn.net/u013793399/article/details/51319235
这里只贴出代码
//generate a half frequency clock of 25MHz always@(posedge(clk)) begin clk_25M <= ~clk_25M; //600*480 resolution needs 25MHz end /*generate the hs && vs timing*/ always@(posedge(clk_25M)) begin /*conditions of reseting Hcnter && Vcnter*/ if( Hcnt == PLD-1 ) //have reached the edge of one line begin Hcnt <= 0; //reset the horizontal counter if( Vcnt == LFD-1 ) //only when horizontal pointer reach the edge can the vertical counter increase Vcnt <=0; else Vcnt <= Vcnt + 1; end else Hcnt <= Hcnt + 1; /*generate hs timing*/ if( Hcnt == PAL - 1 + HFP) hs <= 1'b0; else if( Hcnt == PAL - 1 + HFP + HPW ) hs <= 1'b1; /*generate vs timing*/ if( Vcnt == LAF - 1 + VFP ) vs <= 1'b0; else if( Vcnt == LAF - 1 + VFP + VPW ) vs <= 1'b1; end
需要注意的是,这里必须先进行二分频,因为600*480分辨率需要25MHz,多了不行,少了也不行。选用这个分辨率纯粹是因为25MHz比较好得到,如果是800*600分辨率,需要40MHz,我们只有50MHz时钟,比较麻烦。
显示小球与挡板
我们定出小球的圆心坐标(ball_x_pos,ball_y_pos),以及小球半径ball_r,其中ball_r是个常数,写在了程序里,坐标则是reg型变量,可以更改,这样小球就可以“移动”了。
同样的,我们定出挡板的四条边的位置,分别为up_pos、down_pos、left_pos、right_pos,都是reg型的变量,所以虽然程序里只让挡板左右移动,但是实际上你也可以让它上下动→_→
代码如下:
//Display the downside bar and the ball always @ (posedge clk_25M) begin // Display the downside bar if (Vcnt>=up_pos && Vcnt<=down_pos && Hcnt>=left_pos && Hcnt<=right_pos) begin Red <= Hcnt[3:1]; Green <= Hcnt[6:4]; Blue <= Hcnt[8:7]; end // Display the ball else if ( (Hcnt - ball_x_pos)*(Hcnt - ball_x_pos) + (Vcnt - ball_y_pos)*(Vcnt - ball_y_pos) <= (ball_r * ball_r)) begin Red <= Hcnt[3:1]; Green <= Hcnt[6:4]; Blue <= Hcnt[8:7]; end else begin Red <= 3'b000; Green <= 3'b000; Blue <= 2'b00; end end
- 显示小球的那条长长的不等式,其实就是
为的就是显示一个圆。x2+y2<r2 - 用Hcnt和Vcnt能表示出屏幕上的每一个像素点,在所需要的像素点处改变VGA的颜色输出,其他像素点输出全零(黑色),就可以显示出一个长方形挡板和一个圆形小球。
- 让小球和挡板动起来
上面显示的小球和挡板都是静止的,要让他们动起来,就需要让他们每一帧的位置发生变化,具体来说就是让他们每一帧的坐标都不一样。
代码如下:
//flush the image every frame always @ (posedge vs) begin // movement of the bar if (to_left && left_pos >= LEFT_BOUND) begin left_pos <= left_pos - bar_move_speed; right_pos <= right_pos - bar_move_speed; end else if(to_right && right_pos <= RIGHT_BOUND) begin left_pos <= left_pos + bar_move_speed; right_pos <= right_pos + bar_move_speed; end //movement of the ball if (v_speed == `UP) // go up ball_y_pos <= ball_y_pos - bar_move_speed; else //go down ball_y_pos <= ball_y_pos + bar_move_speed; if (h_speed == `RIGHT) // go right ball_x_pos <= ball_x_pos + bar_move_speed; else //go down ball_x_pos <= ball_x_pos - bar_move_speed; end
要注意的是,这里的触发信号不再是时钟,而是vs场扫描信号。
这是因为我们只需要在每一帧改变一次坐标。
- 让小球懂得反弹
这个游戏的小球总不能飞出屏幕吧,下面的代码实现的就是在小球“撞”到屏幕边缘或者挡板时,能够“反弹”回去。
同时也加上了格挡失败的判定。
//change directions when reach the edge or crush the bar always @ (negedge vs) begin if (ball_y_pos <= UP_BOUND) // Here, all the jugement should use >= or <= instead of == begin v_speed <= 1; // Because when the offset is more than 1, the axis may step over the line lose <= 0; end else if (ball_y_pos >= (up_pos - ball_r) && ball_x_pos <= right_pos && ball_x_pos >= left_pos) v_speed <= 0; else if (ball_y_pos >= down_pos && ball_y_pos < (DOWN_BOUND - ball_r)) begin // Ahhh!!! What the fxxk!!! I miss the ball!!! //Do what you want when lose lose <= 1; end else if (ball_y_pos >= (DOWN_BOUND - ball_r + 1)) v_speed <= 0; else v_speed <= v_speed; if (ball_x_pos <= LEFT_BOUND) h_speed <= 1; else if (ball_x_pos >= RIGHT_BOUND) h_speed <= 0; else h_speed <= h_speed; end
注意到这里对于“冲撞”的判定用的都是‘≤’或者‘≥’而不是‘==’,这是因为如果速度档位调的高,可能会跳过相等的那个像素点,导致飞出屏幕再也回不来。
- 4*七段数码管显示
这个就比较简单了,毕竟原理在数字电路课上都学过。
这里多了一个模块,就是把4位的二进制数字变成7位的数码管数据。
代码如下:
`include "Definition.h"module seg_decoder( input clk, input [3:0] num, output reg [6:0] code );always@(posedge clk)begin case(num) 4'b0000: code <= `ZERO; 4'b0001: code <= `ONE; 4'b0010: code <= `TWO; 4'b0011: code <= `THREE; 4'b0100: code <= `FOUR; 4'b0101: code <= `FIVE; 4'b0110: code <= `SIX; 4'b0111: code <= `SEVEN; 4'b1000: code <= `EIGHT; 4'b1001: code <= `NINE; default: code <= code; endcaseendendmodule
七段数码管的数据写作宏,定义在了Definition.h里面
针对4个数码管,实例化了4个decoder
seg_decoder seg0( .clk(clk), .num(num0), .code(out0) );seg_decoder seg1( .clk(clk), .num(num1), .code(out1) );seg_decoder seg2( .clk(clk), .num(num2), .code(out2) );seg_decoder seg3( .clk(clk), .num(num3), .code(out3) );
扫描显示4个数码管
// Display four segalways@(posedge sclk)begin if(rst) //high active begin cnt <= 0; end else begin case(cnt) 2'b00: begin seg <= out0; select <= 4'b0111; end 2'b01: begin seg <= out1; select <= 4'b1011; end 2'b10: begin seg <= out2; select <= 4'b1101; end 2'b11: begin seg <= out3; select <= 4'b1110; end default: begin seg <= seg; select <= select; end endcase cnt <= cnt + 1; if(cnt == 2'b11) cnt<=0; endend
需要注意,扫描的时钟频率不能过快,因为数码管本身的rising time和falling time就没这么快,如果扫描频率过快,就会全部都显示8
收到lose信号的时候刷新数码管数据,并考虑进位。
// Flush data each time you losealways@(posedge lose or posedge rst)begin if(rst) begin num0 <= 0; num1 <= 0; num2 <= 0; num3 <=0; end else if(num0 == 9) begin num0 <= 0; if(num1 == 9) begin num1 <= 0; if(num2 == 9) begin num2 <= 0; if(num3 == 9) num3 <= 0; else num3 <= num3 + 1; end else num2 <= num2 + 1; end else num1 <= num1 + 1; end else num0 <= num0 + 1;end
至此,所有的功能已经全部实现。
引脚约束
将程序里的“端口”和板子上的“引脚”联系起来,就是ucf文件要做的事情。
由于这个只适用于Basys2开发板,没什么通用性,只贴代码算了。
# clock pin for Basys2 BoardNET "mclk" LOC = "B8"; # Bank = 0, Signal name = MCLKNET "mclk" CLOCK_DEDICATED_ROUTE = FALSE;# Pin assignment for VGANET "HSYNC" LOC = "J14" ; #| DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = HSYNCNET "VSYNC" LOC = "K13" ; #| DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = VSYNCNET "to_left" LOC = "A7" ; NET "to_right" LOC = "G12" ; NET "bar_move_speed<0>" LOC = "P11" ; NET "bar_move_speed<1>" LOC = "L3" ; NET "bar_move_speed<2>" LOC = "K3" ; NET "bar_move_speed<3>" LOC = "B4" ; NET "OutRed<2>" LOC = "F13" ; #| DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = RED2NET "OutRed<1>" LOC = "D13" ; #| DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = RED1NET "OutRed<0>" LOC = "C14" ; #| DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = RED0NET "OutGreen<2>" LOC = "G14" ; #| DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = GRN2NET "OutGreen<1>" LOC = "G13" ; #| DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = GRN1 NET "OutGreen<0>" LOC = "F14" ; #| DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = GRN0 NET "OutBlue<2>" LOC = "J13" ; #| DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = BLU2NET "OutBlue<1>" LOC = "H13" ; #| DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = BLU1 # Pin assignment for 7seg_LEDNET "seg_LED<0>" LOC = "L14" ; NET "seg_LED<1>" LOC = "H12" ; NET "seg_LED<2>" LOC = "N14" ; NET "seg_LED<3>" LOC = "N11" ; NET "seg_LED<4>" LOC = "P12" ; NET "seg_LED<5>" LOC = "L13" ; NET "seg_LED<6>" LOC = "M12" ; NET "seg_select<0>" LOC = "K14" ; NET "seg_select<1>" LOC = "M13" ; NET "seg_select<2>" LOC = "J12" ; NET "seg_select<3>" LOC = "F12" ; NET "rst" LOC = "M4" ;
要注意的是,
NET “mclk” CLOCK_DEDICATED_ROUTE = FALSE;
这句话千万不能少,少了会报错。
另外Basys2有一个mclk,一个uclk。其中uclk是外部时钟,压根没焊上去,不能用,只能用mclk
至此,所有的事情都已经做好了,烧到板子上,连上VGA,就可以玩了。
- Verilog编写VGA显示的小游戏
- VERILOG代码:VGA显示字符串的解读
- verilog VGA显示
- Verilog实现VGA显示控制器
- Verilog VGA 静态显示图片
- 数字电路设计之VGA的字母显示的verilog实现
- 数字电路设计之VGA显示条形图的verilog实现
- verilog实现的VGA显示自反弹移动小方块
- 基于nios II的verilog VGA字符显示控制
- 关于VGA的显示原理和Verilog语言实现/学习笔记
- VGA控制的verilog模块设计
- FPGA的VGA显示
- VGA学习笔记(verilog)
- verilog简单驱动VGA
- Fpga的vga显示设计
- ABAP编写的小游戏
- HTML5编写的小游戏
- HTML5编写的小游戏
- 巧用Shift
- 勿忘心安。
- Java数据类型总结
- 非法指针invalid pointer
- Display中getHeight()和getWidth() 官方废弃
- Verilog编写VGA显示的小游戏
- 详解MySQL中DROP,TRUNCATE 和DELETE的区别实现mysql从零开始
- 《您的设计模式》(CBF4LIFE)之“代理模式”【整理】
- 大型门户网站架构分析
- mysql数据库导出导入
- CS231n 卷积神经网络与计算机视觉 8 手把手实现神经网络分类
- sigma.js框架初探
- 使用RxJava来改进用户体验
- 利用freemarker 静态化网页 多代码