这是一个使用FPGA制作的游戏,能实现Flappy Bird游戏的基本功能。其中参考了许多大神的博客,代码,思路与一些特别的设计。完成大一的数电作业。(整个项目在我的GitHub,项目地址在文末)
按键:UP-上升 RST-重新开始 BEGIN-开始游戏
效果图如下:
接下来我将陆续记录错误汇总以及一些代码实现。
ERROR1:
Error: Selected device has 46 RAM location(s) of type M9K. However, the current design needs more than 46 to successfully fit
Error: Can't place all RAM cells in design
这个问题是FPGA内部ROM资源不足,也就是你存储的东西超过它的内存了。
1解决方法:
压缩图片>优化代码>外置sd卡
ERROR2:
Error: Can't find Memory Initialization File or Hexadecimal (Intel-Format) File ../../../bird.mif for ROM instance
解决方法:这是一个很皮的错误,明明有MIF生成对应HEX文件可是工程就找不到...百度无果后我重新建立了一个ROM就解决了,应该是之前生成的HEX与后面修改来源文件后的HEX冲突了
ERROR3:
Error: Can't resolve multiple constant drivers for net“xx”
解决方法:这种问题属于语法错误,两个或多个always中对同一个变量赋值就会产生这种错误,自行完善下就行。
ERROR4:
变量赋值错误
这种错误可能编译器是找不出来的,初学者很容易犯。注意寄存器的位数设置与所需位数是否吻合。
其中具体的代码实现后面作业写完再更
加入了一些新的功能(死亡,通关,计分),完善了随机模块
以下为代码实现思路:
核心控制代码 > 读取ROM > VGA显示
笔者认为,VGA其实很简单,就和加强版点阵差不多,就多了点时序控制。
以下为VGA信号时序
如图,在VGA的时序里,并不是所有的时间都是在输出显示信号(只在Active vidio time输出显示信号),还有一部分时间
是用来同步的。
所以我们要把握好以下几个量:
valid (显示信号有效段)
hsync(同步信号) = (hcount > hsync_end);
vsync (同步信号) = (vcount > vsync_end); 与上图一直即可(注意频率不同低电平时间不同)
计数器转成对应分辨率,方便开发
assign hc = hcount - hdat_begin;
assign vc = vcount - vdat_begin;
驱动好了以后你就可以在屏幕上显示一些东西了!
最简单的启动代码(600*800分辨率)
module vga(clk,rst,hsync,vsync,x_cnt,y_cnt,valid);
input clk;
input rst;
output hsync;
output vsync;
output [10:0]x_cnt;
output [9:0]y_cnt;
output valid;
reg[10:0]x_cnt; //行坐标
reg[9:0]y_cnt; //列坐标
always@(posedge clk or negedge rst)
if(!rst) x_cnt <= 11'd0;
else if (x_cnt == 11'd1039) x_cnt <= 11'd0;
else x_cnt <= x_cnt + 1'b1;
always@(posedge clk or negedge rst)
if(!rst) y_cnt <= 10'd0;
else if (y_cnt == 10'd665) y_cnt <= 10'd0;
else if (x_cnt == 11'd1039)y_cnt <= y_cnt + 1'b1;
wire valid ; //有效显示区标志
assign valid = (x_cnt >= 11'd176)&&(x_cnt <=11'd976)
&&(y_cnt >= 10'd43)&&(y_cnt < 10'd643);
wire[9:0]xpos,ypos; //有效显示区坐标
assign xpos = x_cnt - 11'd176;
assign ypos = y_cnt - 10'd43;
reg hsync_r,vsync_r;
always@(posedge clk or negedge rst)
if(!rst) hsync_r <= 1'b1;
else if (x_cnt == 11'd0) hsync_r <= 1'b0; //产生hsync信号
else if (x_cnt == 11'd120) hsync_r <= 1'b1;
always@(posedge clk or negedge rst_n)
if(!rst) vsync_r <= 1'b1;
else if (y_cnt == 10'd0) vsync_r <= 1'b0; //产生vsync信号
else if (y_cnt == 10'd6) vsync_r <= 1'b1;
assign hsync = hsync_r;
assign vsync = vsync_r;
endmodule
有很多朋友在问我如何将自己的图片显示到屏幕上,我来说一下我的方法
首先得有一张BMP格式的图片(不是的转化一下就好),然后对它取模生成MIF文件(软件为bmptomif),然后再将MIF存入ROM中即可
MIF如图 可以看到每个地址对应了3位的RGB数据(红绿蓝三色) 并在开头说明了地址有多少和每一个地址存了多少数据(这些数据与后面设置ROM有关系)
控制部分要对数据进行分类(什么时候该出现在屏幕哪里)并加上一点控制来实现游戏效果
我们要对几个功能进行分析及解决实现
开始界面
游戏进行界面
死亡界面
胜利界面
其中主要设计游戏运行界面
如何把游戏在屏幕上动态显示呢?我们需要让静态的图片动起来(这里不需要太完美像动画一样真正动起来,只是图片简单移动,有能力的可以试试动画播放)
把小鸟看作像素块的堆叠,小鸟的移动就是对应像素块坐标的移动,那么绿色水管的像素快和小鸟像素块坐标有重叠的时候判断小鸟死亡。是不是很简单(当然其中水管的生成要随机)。
关于计分模块...我在写的时候犯了之前解决过的错误:忘了分频。不过这一次我用了另一种方法解决:用逻辑判断来避免重复计数。
always @ (posedge vga_clk )
begin
if((push <= move_x||push1 <= move_x||push2 <= move_x||push3 <= move_x)&&!ad)
begin
score <= score + 1'b1;
ad <= 1;
end
else if(push > move_x&&push1 > move_x&&push2 > move_x&&push3 > move_x)
ad <= 0;
if(score == 30)
begin
win <= 1;
end
if(!reset)
begin
win <= 0;
score <= 0;
end
end
嗯~ o(* ̄▽ ̄*)o每想出一种新方法,就会有一种新感觉
总结:从零开始FPGA,学习Verilog语法和VGA时序,码核心代码,调完善。后又花一上午改进完善并记录这篇博客。过程中最难受的是一开始,以C的思路去想却不知道怎么写,更是无从下手,自己百度或者想办法一步一步试,后来慢慢有一点点并行(或许和多线程有异曲同工之妙?(后续补充:FPGA是由硬件实现并行,而线程多是软件并发,除非CPU多核多线程也能并行))的感觉(FPGA真奇妙)和完成了大作业。
换一种代码换一种思路,挺舒服的~欢迎留言指出不足,项目GitHub地址:https://github.com/BlusLiu/Flappy-Bird