typora-copy-images-to |
---|
ipic |
数字逻辑课程 郝广博 桂晓琬
Chrome浏览器在没有网络连接的时候,会显示一个小恐龙,点击空格就可以玩这个小游戏,十分有趣,所以我们在这次的project中使用Verilog硬件描述语言来实现这样一个小恐龙游戏。
实验平台: Sword Kintex7
开发环境: Xilinx ISE 14.7
硬件描述语言:Verilog HDL
输入: Sword 实验平台上的按钮、拨动开关
输出: VGA显示器
工程一共包括六个模块,关系如下图:
下面是各个模块的具体介绍
主模块,控制游戏的开始(start)、结束(stop)、重启(reset),以及衔接各个模块,如,把ground
、jump
、cactus
、score
的输出数据送给vga
模块来显示在屏幕上。
首先,我们设计了一个寄存器game_status
用来表示游戏是否是正在进行,0表示stop,1表示running,并且把game_status的引脚接给各个子模块从而实现全局的协调控制。
另外,我们设计了一个寄存器trigger_start
和一条线trigger_stop
。并且在每个时钟周期,都去判断这两个值,如果trigger_stop
为1,就把game_status
变成0,如果trigger_start
为1,就把gama_status
变成1。不过值得一提的是,在start操作的时候,我们并不是发现trigger_start
等于1以后就立即改变game_status
的值,而是等到vs
为低电平的时候再去做这个操作,这是为了保证游戏的开始是在VGA的消隐期进行的。
而游戏停止的机制也十分简单,只需要判断dinosaur和cactus的图案是否有重叠部分,一旦发现某个像素px_dinosaur
和px_cactus
都为1,就表示dinosaur和cactus撞在了一起,即可出发停止游戏的操作。
wire trigger_stop;
assign trigger_stop = px_dinosaur && px_cactus;
reg [31:0]clkdiv;
always@(posedge CLK) begin
clkdiv <= clkdiv + 1'b1;
end
一个32位的分频器。
有必要在介绍其他模块前,先说明一下vga显示模块的一些引脚定义。
这个模块基本是在老师提供的Vgac模块的基础上做了一些修改。
由于不需要显示彩色的图案,所以我们简化了r、g、b的数据处理:
assign r = rdn ? 4'h0 : px ? 4'b0000:4'b1111; // 3-bit red
assign g = rdn ? 4'h0 : px ? 4'b0000:4'b1111; // 3-bit green
assign b = rdn ? 4'h0 : px ? 4'b0000:4'b1111; // 2-bit blue
px
为1,则显示黑色,px
为0,则显示白色。
而px
这根线的定义则是:
assign px = px_ground || px_dinosaur || px_cactus || px_score;
也就是说,只要px_ground
、px_dinosaur
、px_cactus
、px_score
其中有一个为1,px
就会为1,使屏幕上对应的位置显示黑色像素。
这个原理类似于投影重叠,如下图,layerA和layerB分别有一个三角形和一个长方形,因此screen上的投影图案显示的是两个图形的叠加。
在这个工程里,我们从ground
、jump
、cactus
、score
这几个模块分别引了一根线到vga
模块,并且从vga
模块引出col_addr
和row_addr
这两组线,来告诉其他模块此时需要显示的处于哪个位置的像素,这样,其他模块只需要根据col_addr
和row_addr
计算出自己要在这个位置显示黑色还是白色像素就可以了,vga
模块会把这些独立的图像进行叠加。
除此以外,我们还把vga
模块的vs
这根线接到了其他的模块,以便于一些操作可以控制在消隐期(blanking period)进行。
相较于做显存的方案,我们采用的这种方法可能有些简陋,但是对于这个小游戏,足矣。
一些其他的基本参数
刷新频率:60Hz
屏幕宽度:640px (col)
屏幕高度:480px (row)
这个模块控制路面的显示和移动,不涉及太多的逻辑。
所谓的路面,其实是由4块8*160的地砖构成的,每一块的图案都是首尾相接的,因此只需要一个公式,就能算出px
的值:
px <= pattern[row_addr-10'd400][(col_addr+ground_position)%10'd160];
而通过
always @(negedge fresh) begin
if (game_status) begin
ground_position<=(ground_position+speed)%10'd160;
end
end
可以在game_status
等于1的时候,每经过一帧,就改变一点路面的位置。
这个模块用来显示小恐龙以及控制小恐龙的跳跃。
我们把小恐龙切成了两个部分,并且开了5个ram,分别用来存小恐龙的身体的两个状态和脚的三个状态。这样可以让小恐龙在跑、跳、停止这三个状态下可以显示不同的样子,更加生动。
对于jump的过程,实际上是有两个变量的:height
和time
,但是,我们只是在每一帧改变time
的值,而学过物理的同学都知道,小恐龙在竖直方向上是做匀加速直线运动,于是有:
assign height = (jump_time*12'd40 - jump_time*jump_time) / 2'd2;
这样就可以比较逼真的模拟出重力的感觉。
这个模块是用来显示仙人掌的,这里,有两个地方需要做到随机(至少是伪随机):两个仙人掌之间的间距、仙人掌的种类。但是verilog内的$random
函数只能用于仿真,因此我们去stackoverflow查了一番,找到了这样一个伪随机数生成的方法:(使用CFSR)
always @(posedge clk) begin
data_next[4] = data[4]^data[1];
data_next[3] = data[3]^data[0];
data_next[2] = data[2]^data_next[4];
data_next[1] = data[1]^data_next[3];
data_next[0] = data[0]^data_next[2];
end
always @(posedge clk)
if(RESET)
data <= 5'h1f;
else
data <= data_next;
比较遗憾的是,由于时间有限,我们只做了大中小三种仙人掌,没有设计更多的障碍物种类。
这个模块用于统计并输出分数。
我们在module前利用宏定义``define`定义了单词“score”和四位数字0-9.
此处以字母“S”为例。
S是利用五个矩形简单表示的,其他字母和数字同理。
`define LetterS ((450 <= col_addr) && (col_addr < 470) && (30 <= row_addr) && (row_addr < 34)) || ((450 <= col_addr) && (col_addr < 470) && (48 <= row_addr) && (row_addr < 52))
|| ((450 <= col_addr) && (col_addr < 470) && (66 <= row_addr) && (row_addr < 70))
|| ((450 <= col_addr) && (col_addr < 454) && (30 <= row_addr) && (row_addr < 52))
|| ((466 <= col_addr) && (col_addr < 470) && (48 <= row_addr) && (row_addr < 70))
在每一个时钟周期,我们都会进行分数输出。
如果reset,则不输出。
其他情况,输出“score”和分数。分数有四位,每一位由四位二进制表示,输出时每四位判断应输出的数字。
always @ (posedge clkdiv[0] or posedge RESET) begin
if(RESET==1)begin
px<=1'b0;//if reset , don't display the score
end
else if(`LetterS || `LetterC || `LetterO || `LetterR || `LetterE) begin
px<=1'b1; //display score
end
else if(((score[3:0] == 0) && (`Zero0)) ||//every 4 regs shows one number.
((score[3:0] == 4'd1) && (`One0)) ||
...
) begin
px<=1'b1;
end
else begin
px<=1'b0;
end
end
每个时钟周期,我们都进行分数的更新。
如果reset或start,分数清零。
如果game over,分数不变化。
如果游戏正常进行,则分数增加。特别的,由于储存方式为四位二进制表示一个十进制的数,我们需要转换成BCD码,即如果达到9(4‘b1001),清零当前位并进位。
if(score[15:12] == 4'b1001) begin
score[15:12] <= 0;
end
else score[15:12] <= score[15:12] + 1;
initial begin
// Initialize Inputs
vga_clk = 0;
clrn = 0;
px_ground = 0;
px_score=0;
px_dinosaur=0;
px_cactus=0;
// Wait 100 ns for global reset to finish
#30;
clrn = 1;
// Add stimulus here
end
always begin
vga_clk = 0; #10;
vga_clk = 1; #10;
end
always begin
px_ground=0;
#100000;
px_ground=1;
#50000;
end
initial begin
// Initialize Inputs
clkdiv = 0;
row_addr = 0;
col_addr = 0;
game_status = 0;
fresh=0;
// Wait 100 ns for global reset to finish
#100;
game_status =1;
end
always begin
clkdiv = clkdiv+1'b1; #10;
end
always begin
fresh=0;#30;
fresh=1;#30;
end
initial begin
// Initialize Inputs
clk = 0;
RESET = 0;
// Wait 100 ns for global reset to finish
#30;
RESET=1;
#30;
RESET=0;
end
always begin
clk = 0; #10;
clk = 1; #10;
end
- VGA模块
- Cactus模块
- Random模块
- 文档书写
- 仿真测试
- 模块设计
- Jump模块
- Ground模块
- Frame模块
- 文档书写
- 流程图制作
- 图案设计
- 视频制作