一、设计内容及模块划分
在DE2开发平台上用按键、LCD及VGA制作贪吃蛇小游戏。
贪吃蛇游戏的设计可以分为两部分,第一部分是游戏的显示,另一部分是对游戏的控制。
在显示部分使用VGA驱动在屏幕上显示游戏画面,用LCD显示实时分数分数。因此需要VGA的驱动模块、VGA显示模块、LCD驱动模块、LCD显示模块。
在游戏控制部分,首先游戏是由按键控制,所以需要一个按键模块。控制又分为游戏状态的控制,蛇身行进的控制,以及食物生成的控制。所以这一部分又分为这几个模块。
再按照每个模块所需要的功能进行硬件描述,最后在顶层文件实现例化调用。
二、各模块设计
2.1 按键模块
DE2品台上KEY0/1/2/3信号默认为高电平,当按键按下信号变为低电平,通过这个变化来产生一个控制信号。本设计不加按键消抖也不会对设计有影响,但为了严谨性,以及模块的可移植性,加入了按键消抖。
此设计中的消抖的思想属于软件消抖的思想,基于延时检测。通过将当前的按键状态存储在预先设置好的寄存器中。
通过Key_left <= Key_left2&&(!Left);只有当原先为高电平且15ms后为低电平才判定为按键按下。
这种方法其实十分地浪费资源,更多消抖方法可以查阅其它相关资料(如硬件架构的艺术)
module Key_check
(
input Clk_50mhz,
input Rst_n,
input Left,
input Right,
input Up,
input Down,
output reg Key_left,
output reg Key_right,
output reg Key_up,
output reg Key_down
);
/******************************************************************/
reg [31:0] Count1;
reg Key_left2;
reg Key_right2;
reg Key_up2;
reg Key_down2;
always @ (posedge Clk_50mhz or negedge Rst_n)
begin
if(!Rst_n)
begin
Count1 <= 32'd0;
Key_left <= 1'd0;
Key_right <= 1'd0;
Key_up <= 1'd0;
Key_down <= 1'd0;
Key_left2 <= 1'd0;
Key_right2 <= 1'd0;
Key_up2 <= 1'd0;
Key_down2 <= 1'd0;
end
else
begin
if(Count1 == 32'd750_000) //15MS
begin
Count1 <= 32'd0;
Key_left <= Key_left2&&(!Left);
Key_right <= Key_right2&&(!Right);
Key_up <= Key_up2&&(!Up);
Key_down <= Key_down2&&(!Down);
Key_left2 <= Left;
Key_right2 <= Right;
Key_up2 <= Up;
Key_down2 <= Down;
end
else
begin
Count1 <= Count1 + 32'd1;
Key_left <= 1'd0;
Key_right <= 1'd0;
Key_up <= 1'd0;
Key_down <= 1'd0;
end
end
end
endmodule
2.2 游戏控制模块
游戏控制指对游戏状态的控制,我们将游戏分为3个状态,分别是游戏开始的准备状态相当于空闲状态,游戏进行状态,以及游戏结束状态。通过一个二段式状态机来描述状态的跳转和每个状态所处理的事情。详细看代码注解部分:
module Game_ctrl //game control
(
input Clk_50, //50MHz时钟
input rst_n, //复位信号低电平有效
input Key_left, //左按键按下
input Key_right, //右按键按下
input Key_up, //上按键按下
input Key_down, //下按键按下
input Hit_wall, //撞到墙的信号
input Hit_body, //撞到身体的信号
output reg [2:0] game_status, //输出游戏状态
output status_flg //状态转换时产生脉冲信号(计分模块用)
);
parameter START = 3'b000;
parameter PLAY = 3'b001;
parameter DIE = 3'b010;
reg [2:0] current_state,next_state;
//状态转换时产生脉冲信号
assign status_flg=(game_status != next_state)? 1'b1:1'b0;
//状态输出
always @(posedge Clk_50 or negedge rst_n)
begin
if(!rst_n)
game_status <= START;
else
game_status <= current_state;
end
/****************************************/
//FSM1-状态机跳转
always @(posedge Clk_50 or negedge rst_n)
begin
if(!rst_n)
current_state <= START;
else
current_state <= next_state;
end
/****************************************/
//FSM2
always @(*)
begin
case(current_state) //START 状态:有按键按下进入PLAY游戏状态
START:
begin
if(Key_left | Key_right | Key_up | Key_down)
next_state = PLAY;
else next_state = START;
end
//PLAY状态:出现死亡信号进入游戏结束状态(DIE)
PLAY :
begin
if(Hit_wall| Hit_body)
next_state = DIE;
else next_state = PLAY;
end
//游戏结束状态,有按键按下恢复到游戏准备状态
DIE : begin
if(Key_left | Key_right | Key_up | Key_down)
next_state = START;
else next_state = DIE;
end
default: next_state = START;
endcase
end
endmodul
2.3 蛇身行进控制模块
module snake_ctrl
(
input Clk_50mhz,
input Rst_n,
input [31:0]speed,
input Key_left,
input Key_right,
input Key_up,
input Key_down,
output reg [1:0] Object, // 00-NONE- 01-HEAD- 10-BODY- 11-WALL-
input [10:0] Pixel_x,
input [10:0] Pixel_y,
output [5:0] Head_x,
output [5:0] Head_y,
input Body_add_sig,
input [2:0] Game_status, //START-000 -PLAY-001 -END010
output reg Hit_body_sig,
output reg Hit_wall_sig
);
/***************************************************************************/
localparam Up = 2'b00;
localparam Down = 2'b01;
localparam Left = 2'b10;
localparam Right = 2'b11;
localparam NONE = 2'b00;
localparam HEAD = 2'b01;
localparam BODY = 2'b10;
localparam WALL = 2'b11;
parameter END = 3'b010;
parameter START = 3'b000;
parameter PLAY = 3'b001;
parameter x_num=6'd39;
parameter y_num=6'd29;
/***************************************************************************/
reg [6:0] Body_num;
reg [31:0] Count;
wire [1:0] Direct;
reg [1:0] Direct_r;
reg [1:0] Direct_next;
assign Direct = Direct_r;
reg Direct_left;
reg Direct_right;
reg Direct_up;
reg Direct_down;
reg [5:0] Body_x [15:0];
reg [5:0] Body_y [15:0];
reg [15:0] Snake_light_sig;
assign Head_x = Body_x[0];
assign Head_y = Body_y[0];
/***************************************************************************/
always @ (posedge Clk_50mhz or negedge Rst_n)
begin
if(!Rst_n)
Direct_r <= Right;//???????????
else
Direct_r <= Direct_next;
end
/***************************************************************************/
always @ (posedge Clk_50mhz or negedge Rst_n)
begin
if(!Rst_n)
begin
Count <= 32'd0;
Body_x[0] <= 6'd20;
Body_y[0] <= 6'd12;
Body_x[1] <= 6'd19;
Body_y[1] <= 6'd12;
Body_x[2] <= 6'd18;
Body_y[2] <= 6'd12;
Body_x[3] <= 6'd0;
Body_y[3] <= 6'd0;
Body_x[4] <= 6'd0;
Body_y[4] <= 6'd0;
Body_x[5] <= 6'd0;
Body_y[5] <= 6'd0;
Body_x[6] <= 6'd0;
Body_y[6] <= 6'd0;
Body_x[7] <= 6'd0;
Body_y[7] <= 6'd0;
Body_x[8] <= 6'd0;
Body_y[8] <= 6'd0;
Body_x[9] <= 6'd0;
Body_y[9] <= 6'd0;
Body_x[10] <= 6'd0;
Body_y[10] <= 6'd0;
Body_x[11] <= 6'd0;
Body_y[11] <= 6'd0;
Body_x[12] <= 6'd0;
Body_y[12] <= 6'd0;
Body_x[13] <= 6'd0;
Body_y[13] <= 6'd0;
Body_x[14] <= 6'd0;
Body_y[14] <= 6'd0;
Body_x[15] <= 6'd0;
Body_y[15] <= 6'd0;
Hit_wall_sig <= 1'd0;
Hit_body_sig <= 1'd0;
end
else if((Game_status == END))
begin
Body_x[0] <= Body_x[0];
Body_y[0] <= Body_y[0] ;
Body_x[1] <= Body_x[1] ;
Body_y[1] <= Body_y[1];
Body_x[2] <= Body_x[2] ;
Body_y[2] <= Body_y[2];
Body_x[3] <= Body_x[3] ;
Body_y[3] <= Body_y[3];
Body_x[4] <= Body_x[4] ;
Body_y[4] <= Body_y[4] ;
Body_x[5] <= Body_x[5] ;
Body_y[5] <= Body_y[5];
Body_x[6] <= Body_x[6] ;
Body_y[6] <= Body_y[6];
Body_x[7] <= Body_x[7];
Body_y[7] <= Body_y[7] ;
Body_x[8] <= Body_x[8];
Body_y[8] <= Body_y[8];
Body_x[9] <= Body_x[9] ;
Body_y[9] <= Body_y[9];
Body_x[10] <= Body_x[10];
Body_y[10] <= Body_y[10];
Body_x[11] <= Body_x[11] ;
Body_y[11] <= Body_y[11];
Body_x[12] <= Body_x[12];
Body_y[12] <= Body_y[12] ;
Body_x[13] <= Body_x[13];
Body_y[13] <= Body_y[13];
Body_x[14] <= Body_x[14];
Body_y[14] <= Body_y[14];
Body_x[15] <= Body_x[15];
Body_y[15] <= Body_y[15];
//Hit_wall_sig <= 1'd0;
//Hit_body_sig <= 1'd0;
end
/************************88RST***************************/
else if(Count == speed) //0.02us*12'500'000 = 0.25s???????
begin
Count <= 32'd0;
//************************************************????
if((Game_status == START))
//if(Game_status == PLAY)
begin
Body_x[0] <= 6'd20;
Body_y[0] <= 6'd12;
Body_x[1] <= 6'd19;
Body_y[1] <= 6'd12;
Body_x[2] <= 6'd18;
Body_y[2] <= 6'd12;
Body_x[3] <= 6'd0;
Body_y[3] <= 6'd0;
Body_x[4] <= 6'd0;
Body_y[4] <= 6'd0;
Body_x[5] <= 6'd0;
Body_y[5] <= 6'd0;
Body_x[6] <= 6'd0;
Body_y[6] <= 6'd0;
Body_x[7] <= 6'd0;
Body_y[7] <= 6'd0;
Body_x[8] <= 6'd0;
Body_y[8] <= 6'd0;
Body_x[9] <= 6'd0;
Body_y[9] <= 6'd0;
Body_x[10] <= 6'd0;
Body_y[10] <= 6'd0;
Body_x[11] <= 6'd0;
Body_y[11] <= 6'd0;
Body_x[12] <= 6'd0;
Body_y[12] <= 6'd0;
Body_x[13] <= 6'd0;
Body_y[13] <= 6'd0;
Body_x[14] <= 6'd0;
Body_y[14] <= 6'd0;
Body_x[15] <= 6'd0;
Body_y[15] <= 6'd0;
Hit_wall_sig <= 1'd0;
Hit_body_sig <= 1'd0;
end
/**************START-DIE***************************/
else
begin
if(((Direct == Up) && (Body_y[0] == 6'd1)) | ((Direct == Down) && (Body_y[0] == (y_num-1))) | ((Direct == Left) && (Body_x[0] == 6'd1)) | ((Direct == Right) && (Body_x[0] == (x_num-1))))
Hit_wall_sig <= 1'd1;
else if(((Body_y[0] == Body_y[1]) && (Body_x[0] == Body_x[1]) && (Snake_light_sig[1] == 1'd1)) |
((Body_y[0] == Body_y[2]) && (Body_x[0] == Body_x[2]) && (Snake_light_sig[2] == 1'd1)) |
((Body_y[0] == Body_y[3]) && (Body_x[0] == Body_x[3]) && (Snake_light_sig[3] == 1'd1)) |
((Body_y[0] == Body_y[4]) && (Body_x[0] == Body_x[4]) && (Snake_light_sig[4] == 1'd1)) |
((Body_y[0] == Body_y[5]) && (Body_x[0] == Body_x[5]) && (Snake_light_sig[5] == 1'd1)) |
((Body_y[0] == Body_y[6]) && (Body_x[0] == Body_x[6]) && (Snake_light_sig[6] == 1'd1)) |
((Body_y[0] == Body_y[7]) && (Body_x[0] == Body_x[7]) && (Snake_light_sig[7] == 1'd1)) |
((Body_y[0] == Body_y[8]) && (Body_x[0] == Body_x[8]) && (Snake_light_sig[8] == 1'd1)) |
((Body_y[0] == Body_y[9]) && (Body_x[0] == Body_x[9]) && (Snake_light_sig[9] == 1'd1)) |
((Body_y[0] == Body_y[10]) && (Body_x[0] == Body_x[10]) && (Snake_light_sig[10] == 1'd1)) |
((Body_y[0] == Body_y[11]) && (Body_x[0] == Body_x[11]) && (Snake_light_sig[11] == 1'd1)) |
((Body_y[0] == Body_y[12]) && (Body_x[0] == Body_x[12]) && (Snake_light_sig[12] == 1'd1)) |
((Body_y[0] == Body_y[13]) && (Body_x[0] == Body_x[13]) && (Snake_light_sig[13] == 1'd1)) |
((Body_y[0] == Body_y[14]) && (Body_x[0] == Body_x[14]) && (Snake_light_sig[14] == 1'd1)) |
((Body_y[0] == Body_y[15]) && (Body_x[0] == Body_x[15]) && (Snake_light_sig[15] == 1'd1)))
Hit_body_sig <= 1'd1;
else
begin
Body_x[1] <= Body_x[0];
Body_y[1] <= Body_y[0];
Body_x[2] <= Body_x[1];
Body_y[2] <= Body_y[1];
Body_x[3] <= Body_x[2];
Body_y[3] <= Body_y[2];
Body_x[4] <= Body_x[3];
Body_y[4] <= Body_y[3];
Body_x[5] <= Body_x[4];
Body_y[5] <= Body_y[4];
Body_x[6] <= Body_x[5];
Body_y[6] <= Body_y[5];
Body_x[7] <= Body_x[6];
Body_y[7] <= Body_y[6];
Body_x[8] <= Body_x[7];
Body_y[8] <= Body_y[7];
Body_x[9] <= Body_x[8];
Body_y[9] <= Body_y[8];
Body_x[10] <= Body_x[9];
Body_y[10] <= Body_y[9];
Body_x[11] <= Body_x[10];
Body_y[11] <= Body_y[10];
Body_x[12] <= Body_x[11];
Body_y[12] <= Body_y[11];
Body_x[13] <= Body_x[12];
Body_y[13] <= Body_y[12];
Body_x[14] <= Body_x[13];
Body_y[14] <= Body_y[13];
Body_x[15] <= Body_x[14];
Body_y[15] <= Body_y[14];
case(Direct)
Up:
begin
if(Body_y[0] == 6'd1)
Hit_wall_sig <= 1'd1;
else
Body_y[0] <= Body_y[0] - 6'd1;
end
Down:
begin
if(Body_y[0] == (y_num-1))
Hit_wall_sig <= 1'd1;
else
Body_y[0] <= Body_y[0] + 6'd1;
end
Left:
begin
if(Body_x[0] == 6'd1)
Hit_wall_sig <= 1'd1;
else
Body_x[0] <= Body_x[0] - 6'd1;
end
Right:
begin
if(Body_x[0] == (x_num-1))
Hit_wall_sig <= 1'd1;
else
Body_x[0] <= Body_x[0] + 6'd1;
end
endcase
end
end
end
else
Count <= Count + 32'd1;
end
/***************************************************************************/
always @ (posedge Clk_50mhz)
begin
if(Key_left == 1'd1)
Direct_left <= 1'd1;
else if(Key_right == 1'd1)
Direct_right <= 1'd1;
else if(Key_up == 1'd1)
Direct_up <= 1'd1;
else if(Key_down == 1'd1)
Direct_down <= 1'd1;
else
begin
Direct_left <= 1'd0;
Direct_right <= 1'd0;
Direct_up <= 1'd0;
Direct_down <= 1'd0;
end
end
/***************************************************************************/
always @ (*)
begin
Direct_next = Right;
case(Direct)
Up:
begin
if(Direct_left)
Direct_next = Left;
else if(Direct_right)
Direct_next = Right;
else
Direct_next = Up;
end
Down:
begin
if(Direct_left)
Direct_next = Left;
else if(Direct_right)
Direct_next = Right;
else
Direct_next = Down;
end
Left:
begin
if(Direct_up)
Direct_next = Up;
else if(Direct_down)
Direct_next = Down;
else
Direct_next = Left;
end
Right:
begin
if(Direct_up)
Direct_next = Up;
else if(Direct_down)
Direct_next = Down;
else
Direct_next = Right;
end
endcase
end
/***************************************************************************/
reg Eaten_sig;
always @ (posedge Clk_50mhz or negedge Rst_n)
begin
if(!Rst_n)
begin
Snake_light_sig <= 16'd7;
Body_num <= 7'd3;
Eaten_sig <= 1'd0;
end
else if(Game_status == END)
begin
Eaten_sig <= 1'd0;
Snake_light_sig <= Snake_light_sig;
end
else if(Game_status == START)
begin
Body_num <= 7'd3;
Eaten_sig <= 1'd0;
Snake_light_sig <= 16'd7;
end
else
begin
case(Eaten_sig)
1'd0 :
begin
if(Body_add_sig)
begin
Body_num <= Body_num + 7'd1;
Snake_light_sig[Body_num] <= 1'b1;
Eaten_sig <= 1'd1;
end
else
Snake_light_sig[Body_num] <= Snake_light_sig[Body_num];
end
1'd1 :
begin
if(!Body_add_sig)
Eaten_sig <= 0;
else
Eaten_sig <= Eaten_sig;
end
endcase
end
end
always @ (Pixel_x or Pixel_y or Snake_light_sig)
begin
if((Pixel_x >= 11'd0) && (Pixel_x < 11'd800) && (Pixel_y >= 11'd0) && (Pixel_y < 11'd600))
begin
if(Pixel_x[9:4] == 6'd0 |
Pixel_y[9:4] == 6'd0 |
Pixel_x[9:4] == x_num |
Pixel_y[9:4] == y_num)
Object = WALL;
else if(Pixel_x[9:4] == Body_x[0] && Pixel_y[9:4] == Body_y[0] && Snake_light_sig[0] == 1'd1)//?????????
Object = HEAD;
else if(((Pixel_x[9:4] == Body_x[1]) && (Pixel_y[9:4] == Body_y[1]) && (Snake_light_sig[1] == 1'd1)) |
((Pixel_x[9:4] == Body_x[2]) && (Pixel_y[9:4] == Body_y[2]) && (Snake_light_sig[2] == 1'd1)) |
((Pixel_x[9:4] == Body_x[3]) && (Pixel_y[9:4] == Body_y[3]) && (Snake_light_sig[3] == 1'd1)) |
((Pixel_x[9:4] == Body_x[4]) && (Pixel_y[9:4] == Body_y[4]) && (Snake_light_sig[4] == 1'd1)) |
((Pixel_x[9:4] == Body_x[5]) && (Pixel_y[9:4] == Body_y[5]) && (Snake_light_sig[5] == 1'd1)) |
((Pixel_x[9:4] == Body_x[6]) && (Pixel_y[9:4] == Body_y[6]) && (Snake_light_sig[6] == 1'd1)) |
((Pixel_x[9:4] == Body_x[7]) && (Pixel_y[9:4] == Body_y[7]) && (Snake_light_sig[8] == 1'd1)) |
((Pixel_x[9:4] == Body_x[8]) && (Pixel_y[9:4] == Body_y[8]) && (Snake_light_sig[8] == 1'd1)) |
((Pixel_x[9:4] == Body_x[9]) && (Pixel_y[9:4] == Body_y[9]) && (Snake_light_sig[9] == 1'd1)) |
((Pixel_x[9:4] == Body_x[10]) && (Pixel_y[9:4] == Body_y[10]) && (Snake_light_sig[10] == 1'd1)) |
((Pixel_x[9:4] == Body_x[11]) && (Pixel_y[9:4] == Body_y[11]) && (Snake_light_sig[11] == 1'd1)) |
((Pixel_x[9:4] == Body_x[12]) && (Pixel_y[9:4] == Body_y[12]) && (Snake_light_sig[12] == 1'd1)) |
((Pixel_x[9:4] == Body_x[13]) && (Pixel_y[9:4] == Body_y[13]) && (Snake_light_sig[13] == 1'd1)) |
((Pixel_x[9:4] == Body_x[14]) && (Pixel_y[9:4] == Body_y[14]) && (Snake_light_sig[14] == 1'd1)) |
((Pixel_x[9:4] == Body_x[15]) && (Pixel_y[9:4] == Body_y[15]) && (Snake_light_sig[15] == 1'd1)))
Object = BODY;
else
Object = NONE;
end
else
Object = NONE;
end
endmodule
2.4 食物产生模块
蛇的头部、身体以及食物都是用坐标表示的,所选用的分辨率为640X480,以16X16像素为一格。每个时钟周期Random_num都在改变变,而控制蛇吃下食物的时刻却因走法、按键的时间等有所不同,所以不同时刻吃下食物后下一个食物出现的地方近似随机。
module Apple_generate
(
input Clk_50mhz,
input Rst_n,
input [5:0] Head_x,
input [5:0] Head_y,
output reg [5:0] Apple_x,
output reg [4:0] Apple_y,
output reg Body_add_sig
);
/*************************************************************/
reg [31:0] Count1;
reg [10:0] Random_num;
//Random在头的坐标和食物坐标重合时因为CLK频率较高可近似看作随机
always@(posedge Clk_50mhz or negedge Rst_n)
begin
if(!Rst_n)
Random_num <= 11'd0;
else
Random_num <= Random_num + 11'd921;
end
/********************************************************/
always@(posedge Clk_50mhz or negedge Rst_n)
begin
if(!Rst_n)
begin
Count1 <= 32'd0;
//设置初始食物坐标
Apple_x <= 6'd28;
Apple_y <= 5'd13;
Body_add_sig <= 1'd0;
end
else if(Count1 == 32'd250_000)
begin
Count1 <= 32'd0;
if((Apple_x == Head_x) && (Apple_y == Head_y))
begin
Body_add_sig <= 1'd1;
//判断随机数是否超出屏幕坐标范围将随机数转换为下个食物的X Y坐标
Apple_x <= (Random_num[10:5]> 6'd38)? (Random_num[10:5] - 6'd25) : ((Random_num[10:5] == 6'd0)? 6'd1 : Random_num[10:5]);
Apple_y <= (Random_num[4:0] > 5'd28)? (Random_num[4:0] - 5'd3) : ((Random_num[4:0] == 5'd0)? 5'd1 : Random_num[4:0]);
end
else
Body_add_sig <= 1'd0;
end
else
Count1 <= Count1 + 32'd1;
end
endmodule
2.5 计分模块
当头的坐标等于身体坐标值时会产生身体增长信号,当检测到这个信号的上升沿时分数加1,到达到9分时且下一个身体增长信号的上升沿到来则清零,分数的高位加1,并且速度加快,增加游戏难度。代码的其余部分是将所显示字符转化给LCD进行显示。
module score(
input status_flg,
input Body_add_sig,
input rst_n,
input [2:0] game_status,
output [127:0] oscore,
output reg [31:0] ospeed
);
reg [3:0] iscorel;
reg [3:0] iscoreh;
reg [7:0] scorel;
reg [7:0] scoreh;
reg [7:0] char_S;
reg [7:0] char_c;
reg [7:0] char_o;
reg [7:0] char_r;
reg [7:0] char_e;
reg [7:0] maohao;
reg [31:0] kongge;
reg add;
parameter START = 3'b000;
parameter speed2=32'd8_000_000;
parameter speed1=32'd12_500_000;
always @ (posedge status_flg or posedge Body_add_sig or negedge rst_n)
begin
if(!rst_n)
begin
iscorel = 0;
iscoreh = 0;
ospeed = 32'd12_500_000;
end
else if(status_flg)
begin
iscorel = (game_status == START) ? 4'h0:iscorel;
iscoreh = (game_status == START) ? 4'h0:iscoreh;
ospeed = (game_status == START) ? speed1:ospeed;
end
else if( iscorel == 4'd9)
begin
iscorel = 0;
iscoreh = iscoreh +4'd1;
ospeed= speed2;
end
else
iscorel = iscorel +4'd1;
end
always @(*)
begin
scorel=8'h30+iscorel;
scoreh=8'h30+iscoreh;
char_S=8'h53;
char_c=8'h63;
char_o=8'h6f;
char_r=8'h72;
char_e=8'h65;
maohao=8'h3a;
kongge=32'ha0a0a0a0;
end
assign oscore={char_S[7:0],char_c[7:0],char_o[7:0],char_r[7:0],char_e[7:0],maohao[7:0],scoreh[7:0],scorel[7:0],kongge[31:0],kongge[31:0]};
endmodule
2.6 LCD显示模块
三段式状态机的LCD驱动显示模块,通过逐格扫描显示所输入的字符串,这里所显示的格式为score:02(所得分数)。但是这种写法的显示效果不佳,因为每一格都相当于要显示一遍,所以显示所需字符需要扫描整个屏幕,刷新时间久,但是这里字符较少加上人眼的视觉暂留效应整个显示还是可以看清。
module lcd_driver(
input clk,
input rst_n,
input [127:0] line_rom1 ,
input [127:0] line_rom2 ,
output lcd_en, // lcd enable
output reg lcd_rs, // data or cmd cmd_l data_h
output lcd_rw, // read or wire read_h wire_l
output reg [7:0] lcd_data // data
);
/************************************************************/
reg [15:0] cnt;
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt <= 0;
else
cnt <= cnt + 1'b1;
end
assign lcd_en = cnt[15]; //lcd enable,keep same time
assign lcd_rw = 1'b0; //write only
wire cmd_flag = (cnt == 16'h7FFF) ? 1'b1 : 1'b0; //when lcd_en is steady,write a cmd
/**********************************************/
// Gray code : 40 states
parameter IDLE = 8'h00; // IDLE
// lcd init
parameter DISP_SET = 8'h01; // /set 16X2,5X7 ,8 bits record
parameter DISP_OFF = 8'h03; // display off
parameter CLR = 8'h02; // clear the lcd
parameter CURSOR_SET = 8'h06; // CURSOR move 2 right
parameter DISP_ON = 8'h07; // display on
// display 1th line
parameter ROW1_ADDR = 8'h05;
parameter ROW1_0 = 8'h04;
parameter ROW1_1 = 8'h0C;
parameter ROW1_2 = 8'h0D;
parameter ROW1_3 = 8'h0F;
parameter ROW1_4 = 8'h0E;
parameter ROW1_5 = 8'h0A;
parameter ROW1_6 = 8'h0B;
parameter ROW1_7 = 8'h09;
parameter ROW1_8 = 8'h08;
parameter ROW1_9 = 8'h18;
parameter ROW1_A = 8'h19;
parameter ROW1_B = 8'h1B;
parameter ROW1_C = 8'h1A;
parameter ROW1_D = 8'h1E;
parameter ROW1_E = 8'h1F;
parameter ROW1_F = 8'h1D;
// display 2th line
parameter ROW2_ADDR = 8'h1C;
parameter ROW2_0 = 8'h14;
parameter ROW2_1 = 8'h15;
parameter ROW2_2 = 8'h17;
parameter ROW2_3 = 8'h16;
parameter ROW2_4 = 8'h12;
parameter ROW2_5 = 8'h13;
parameter ROW2_6 = 8'h11;
parameter ROW2_7 = 8'h10;
parameter ROW2_8 = 8'h30;
parameter ROW2_9 = 8'h31;
parameter ROW2_A = 8'h33;
parameter ROW2_B = 8'h32;
parameter ROW2_C = 8'h36;
parameter ROW2_D = 8'h37;
parameter ROW2_E = 8'h35;
parameter ROW2_F = 8'h34;
/**************************************************/
// FSM: always1
reg [5:0] current_state, next_state;
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
current_state <= IDLE;
else if(cmd_flag)
current_state <= next_state;
end
/****************************************************/
// FSM: always2
always@*
begin
case(current_state)
// lcd init
IDLE : next_state = DISP_SET;
DISP_SET : next_state = DISP_OFF;
DISP_OFF : next_state = CLR;
CLR : next_state = CURSOR_SET;
CURSOR_SET : next_state = DISP_ON;
DISP_ON : next_state = ROW1_ADDR;
// display 1th line
ROW1_ADDR : next_state = ROW1_0;
ROW1_0 : next_state = ROW1_1;
ROW1_1 : next_state = ROW1_2;
ROW1_2 : next_state = ROW1_3;
ROW1_3 : next_state = ROW1_4;
ROW1_4 : next_state = ROW1_5;
ROW1_5 : next_state = ROW1_6;
ROW1_6 : next_state = ROW1_7;
ROW1_7 : next_state = ROW1_8;
ROW1_8 : next_state = ROW1_9;
ROW1_9 : next_state = ROW1_A;
ROW1_A : next_state = ROW1_B;
ROW1_B : next_state = ROW1_C;
ROW1_C : next_state = ROW1_D;
ROW1_D : next_state = ROW1_E;
ROW1_E : next_state = ROW1_F;
ROW1_F : next_state = ROW2_ADDR;
// display 2th line
ROW2_0 : next_state = ROW2_1;
ROW2_1 : next_state = ROW2_2;
ROW2_2 : next_state = ROW2_3;
ROW2_3 : next_state = ROW2_4;
ROW2_4 : next_state = ROW2_5;
ROW2_5 : next_state = ROW2_6;
ROW2_6 : next_state = ROW2_7;
ROW2_7 : next_state = ROW2_8;
ROW2_8 : next_state = ROW2_9;
ROW2_9 : next_state = ROW2_A;
ROW2_A : next_state = ROW2_B;
ROW2_B : next_state = ROW2_C;
ROW2_C : next_state = ROW2_D;
ROW2_D : next_state = ROW2_E;
ROW2_E : next_state = ROW2_F;
ROW2_F : next_state = ROW1_ADDR;
default : next_state = IDLE ;
endcase
end
/******************************************************/
// FSM: always3
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
lcd_rs <= 0;
lcd_data <= 8'hXX;
end
else if(cmd_flag)
begin
// write_cmd
case(next_state)
IDLE : lcd_rs <= 0; //write_cmd
//lcd init
DISP_SET : lcd_rs <= 0;
DISP_OFF : lcd_rs <= 0;
CLR : lcd_rs <= 0;
CURSOR_SET : lcd_rs <= 0;
DISP_ON : lcd_rs <= 0;
// display 1th line
ROW1_ADDR : lcd_rs <= 0;
ROW1_0 : lcd_rs <= 1; //record
ROW1_1 : lcd_rs <= 1;
ROW1_2 : lcd_rs <= 1;
ROW1_3 : lcd_rs <= 1;
ROW1_4 : lcd_rs <= 1;
ROW1_5 : lcd_rs <= 1;
ROW1_6 : lcd_rs <= 1;
ROW1_7 : lcd_rs <= 1;
ROW1_8 : lcd_rs <= 1;
ROW1_9 : lcd_rs <= 1;
ROW1_A : lcd_rs <= 1;
ROW1_B : lcd_rs <= 1;
ROW1_C : lcd_rs <= 1;
ROW1_D : lcd_rs <= 1;
ROW1_E : lcd_rs <= 1;
ROW1_F : lcd_rs <= 1;
// display 2th line
ROW2_ADDR : lcd_rs <= 0; //statement
ROW2_0 : lcd_rs <= 1;
ROW2_1 : lcd_rs <= 1;
ROW2_2 : lcd_rs <= 1;
ROW2_3 : lcd_rs <= 1;
ROW2_4 : lcd_rs <= 1;
ROW2_5 : lcd_rs <= 1;
ROW2_6 : lcd_rs <= 1;
ROW2_7 : lcd_rs <= 1;
ROW2_8 : lcd_rs <= 1;
ROW2_9 : lcd_rs <= 1;
ROW2_A : lcd_rs <= 1;
ROW2_B : lcd_rs <= 1;
ROW2_C : lcd_rs <= 1;
ROW2_D : lcd_rs <= 1;
ROW2_E : lcd_rs <= 1;
ROW2_F : lcd_rs <= 1;
endcase
// write lcd_data
case(next_state)
IDLE : lcd_data <= 8'hxx;
//lcd init
DISP_SET : lcd_data <= 8'h38; //set 16X2,5X7 ,8 bits record
DISP_OFF : lcd_data <= 8'h08; //off display
CLR : lcd_data <= 8'h01; //clear lcd
CURSOR_SET : lcd_data <= 8'h06; //cursor set
DISP_ON : lcd_data <= 8'h0C; //on display
// display 2th line
ROW1_ADDR : lcd_data <= 8'h80;
ROW1_0 : lcd_data <= line_rom1[127:120];
ROW1_1 : lcd_data <= line_rom1[119:112];
ROW1_2 : lcd_data <= line_rom1[111:104];
ROW1_3 : lcd_data <= line_rom1[103: 96];
ROW1_4 : lcd_data <= line_rom1[ 95: 88];
ROW1_5 : lcd_data <= line_rom1[ 87: 80];
ROW1_6 : lcd_data <= line_rom1[ 79: 72];
ROW1_7 : lcd_data <= line_rom1[ 71: 64];
ROW1_8 : lcd_data <= line_rom1[ 63: 56];
ROW1_9 : lcd_data <= line_rom1[ 55: 48];
ROW1_A : lcd_data <= line_rom1[ 47: 40];
ROW1_B : lcd_data <= line_rom1[ 39: 32];
ROW1_C : lcd_data <= line_rom1[ 31: 24];
ROW1_D : lcd_data <= line_rom1[ 23: 16];
ROW1_E : lcd_data <= line_rom1[ 15: 8];
ROW1_F : lcd_data <= line_rom1[ 7: 0];
// display 2th line
ROW2_ADDR : lcd_data <= 8'hC0;
ROW2_0 : lcd_data <= line_rom2[127:120];
ROW2_1 : lcd_data <= line_rom2[119:112];
ROW2_2 : lcd_data <= line_rom2[111:104];
ROW2_3 : lcd_data <= line_rom2[103: 96];
ROW2_4 : lcd_data <= line_rom2[ 95: 88];
ROW2_5 : lcd_data <= line_rom2[ 87: 80];
ROW2_6 : lcd_data <= line_rom2[ 79: 72];
ROW2_7 : lcd_data <= line_rom2[ 71: 64];
ROW2_8 : lcd_data <= line_rom2[ 63: 56];
ROW2_9 : lcd_data <= line_rom2[ 55: 48];
ROW2_A : lcd_data <= line_rom2[ 47: 40];
ROW2_B : lcd_data <= line_rom2[ 39: 32];
ROW2_C : lcd_data <= line_rom2[ 31: 24];
ROW2_D : lcd_data <= line_rom2[ 23: 16];
ROW2_E : lcd_data <= line_rom2[ 15: 8];
ROW2_F : lcd_data <= line_rom2[ 7: 0];
endcase
end
end
endmodule
2.7 VGA显示模块
对当前坐标所在位置的对象进行判断,输出对应对象的颜色,给VGA驱动模块显示。
module VGA_dis(
input clk_50,
input rst_n,
input [5:0] Apple_x,
input [4:0] Apple_y,
input [10:0] pixel_x,
input [10:0] pixel_y,
input [1:0] object, //00-NONE- 01-HEAD- 10-BODY- 11-WALL-
output reg [9:0] VGA_R,
output reg [9:0] VGA_G,
output reg [9:0] VGA_B
);
parameter NONE = 2’b00;
parameter HEAD = 2’b01;
parameter BODY = 2’b10;
parameter WALL = 2’b11;
always @(posedge clk_50 or negedge rst_n)
begin
if(!rst_n)
begin
VGA_R <= 10'h0;
VGA_G <= 10'h0;
VGA_B <= 10'h0;
end
else if( (Apple_x==pixel_x[9:4]) && (Apple_y==pixel_y[8:4]))
begin
VGA_R <= 10'h3ff;
VGA_G <= 10'h0;
VGA_B <= 10'h0;
end
else
case(object)
NONE: begin
VGA_R <= 10'h0;
VGA_G <= 10'h3ff;
VGA_B <= 10'h0;
end
HEAD: begin
VGA_R <= 10'h3ff;
VGA_G <= 10'h3ff;
VGA_B <= 10'h3ff;
end
BODY: begin
VGA_R <= 10'h0;
VGA_G <= 10'h3ff;
VGA_B <= 10'h3ff;
end
WALL: begin
VGA_R <= 10'h3ff;
VGA_G <= 10'h3ff;
VGA_B <= 10'h3ff;
end
endcase
end
endmodule
2.8 VGA驱动模块
所选的分辨率为640X480,所以首先对DE2上的50M时钟进行2分频得到25M的时钟。在此时钟下通过水平和垂直同步时间参数的设置(如下表)得到VGA(60Hz),分辨率640X480,60Hz即60帧,在这个帧数下人们就可以看清画面看不出有断续。
VGA的颜色输入信号是模拟信号,在DE2平台上,由一块高速视频DA转换器ADV7123将输入代表颜色信息的数字信号经过DAC转换后输出给VGA,其中还有消隐以及同步信号控制其消隐和同步。
VGA的行时序如图所示,当在c时段为有效显示时段,通过相应的分辨率设置时间参数就可以显示,场时序也类似。并将有效时段的位置作为当前位置坐标输出给其它模块使用。仿真结果如同所示,符合工作时序图。
module VGA_Ctrl (
iRed,
iGreen,
iBlue,
oCurrent_X,
oCurrent_Y,
oAddress,
oRequest,
// VGA Side
oVGA_R,
oVGA_G,
oVGA_B,
oVGA_HS,
oVGA_VS,
oVGA_SYNC,
oVGA_BLANK,
oVGA_CLOCK,
// Control Signal
iCLK,
iRST_N );
// Host Side
input [9:0] iRed;
input [9:0] iGreen;
input [9:0] iBlue;
output [21:0] oAddress;
output [10:0] oCurrent_X;
output [10:0] oCurrent_Y;
output oRequest;
// VGA Side
output [9:0] oVGA_R;
output [9:0] oVGA_G;
output [9:0] oVGA_B;
output reg oVGA_HS;
output reg oVGA_VS;
output oVGA_SYNC;
output oVGA_BLANK;
output oVGA_CLOCK;
// Control Signal
input iCLK;
input iRST_N;
// Internal Registers
reg [10:0] H_Cont;
reg [10:0] V_Cont;
reg clk_25M;
always@(posedge iCLK or negedge iRST_N)
begin
if(!iRST_N)
clk_25M <= 0;
else
clk_25M <= ~clk_25M;
end
// Horizontal Parameter
parameter H_FRONT = 13+3; //d front-porch
parameter H_SYNC = 96; //a sync
parameter H_BACK = 45+3; //b back-porch
parameter H_ACT = 640; //c display_valid
parameter H_BLANK = H_FRONT+H_SYNC+H_BACK;
parameter H_TOTAL = H_FRONT+H_SYNC+H_BACK+H_ACT;
// Vertical Parameter
parameter V_FRONT = 9+2; //d
parameter V_SYNC = 2; //a
parameter V_BACK = 32; //b
parameter V_ACT = 480; //c
parameter V_BLANK = V_FRONT+V_SYNC+V_BACK;
parameter V_TOTAL = V_FRONT+V_SYNC+V_BACK+V_ACT;
assign oVGA_SYNC = 1'b1; // This pin is unused.
assign oVGA_BLANK = ~((H_Cont<H_BLANK)||(V_Cont<V_BLANK));
assign oVGA_CLOCK = ~clk_25M;
assign oVGA_R = iRed;
assign oVGA_G = iGreen;
assign oVGA_B = iBlue;
assign oAddress = oCurrent_Y*H_ACT+oCurrent_X;
assign oRequest = ((H_Cont>=H_BLANK && H_Cont<H_TOTAL) && (V_Cont>=V_BLANK && V_Cont<V_TOTAL));
assign oCurrent_X = (H_Cont>=H_BLANK) ? H_Cont-H_BLANK : 11'h0 ;
assign oCurrent_Y = (V_Cont>=V_BLANK) ? V_Cont-V_BLANK : 11'h0 ;
always@(posedge clk_25M or negedge iRST_N)
begin
if(!iRST_N)
begin
H_Cont <= 0;
oVGA_HS <= 1; // a line scan done with a low pulse
end
else
begin
if(H_Cont<H_TOTAL)
H_Cont <= H_Cont+1'b1;
else
H_Cont <= 0;
// Horizontal Sync
if(H_Cont==H_FRONT-1) // Front porch end
oVGA_HS <= 1'b0;
if(H_Cont==H_FRONT+H_SYNC-1) // Sync pulse end
oVGA_HS <= 1'b1;
end
end
always@(posedge oVGA_HS or negedge iRST_N)
begin
if(!iRST_N)
begin
V_Cont <= 0;
oVGA_VS <= 1;
end
else
begin
if(V_Cont<V_TOTAL)
V_Cont <= V_Cont+1'b1;
else
V_Cont <= 0;
// Vertical Sync
if(V_Cont==V_FRONT-1) // Front porch end
oVGA_VS <= 1'b0;
else if(V_Cont==V_FRONT+V_SYNC-1) // Sync pulse end tongbu reset
oVGA_VS <= 1'b1;
end
end
endmodule