1.项目要求
在 ws2812 的矩阵灯阵列上,取一个作为蛇的初始位置和长度,另取一个点 作为食物的初始位置,在此设计中,蛇头用蓝色的点表示,蛇的身体采用红色的 点表示,食物则采用绿色的点进行表示,并且在此设计中还加入了障碍物,障碍 物用黄色的点表示, 游戏开始后,玩家可通过方向键来决定蛇的移动方向,若没有方向键按下, 蛇依然能够朝着原有方向前进,前进速率自拟。移动过程中,如果蛇吃到食物, 蛇的长度增加,并随机生成新的食物。 整个游戏至少能够显示以下几个游戏界面:游戏开始画面(静态显示动态显 示均可)、游戏玩耍界面以及游戏成功或失败界面 当蛇的长度超过设定值以后判定游戏成功(win),撞到自身判定游戏失败 (lose)。
2.ws2812介绍
WS2812 是一种数字可编程 LED 芯片,常用于彩色 LED 灯带和其他 LED 照明产 品中。它采用了一种串行通信协议来控制 LED 的亮度和颜色,其理论原理如下: 时序关系:WS2812b 通过时间间隔的长短来表示数字信号的高低电平,从而 实现数据的传输和控制。每个 WS2812b 芯片都有一个时钟信号,该信号的周期用 来确定数据的传输速率。
数据格式:WS2812b 使用的数据格式是由高位到低位的数据流,并且每个比 特(bit)被分为一个高电平和一个低电平两种状态。一个完整的数据包括 24 个比特,依次代表红、绿、蓝三种颜色通道的亮度值。
数据传输:数据传输通过串行方式进行。每个 WS2812b 芯片接收到一个 24 比特的数据包后,会自动将其中的亮度值转换成对应的颜色,并输出到 LED 灯上。 然后,它将剩余的数据包传递给下一个相邻的 WS2812b 芯片,以此类推,形成了 一个级联的串行链路。
3.系统框图
3.1按键消抖模块
按键消抖模块,用户通过按键按下之后,会产生抖动,这些抖动会导致信号 进行误判,从而引发一些错误,所以按键消抖的设计理念是,当外界用户按下按 键之后,检测是否产生下降沿,通过检测到下降沿,然后进行 20ms 的延时,如 果这是按键还是低电平,则确定按键按下,这时候在这期间进行采样,则不会出 现误判的现象,当检测到上升沿,也就是外界用户松开按键,也会进行 20ms 的 延时,确定是正常松开,这样就完成了一次的按键按下。
3.2游戏控制模块
3.2.1游戏控制模块
因为游戏分为开始画面(静态显示动态显示均可)、游戏玩耍界面以及游戏 成功或失败界面。所以进行了模块化的设计。分为游戏总控模块,开始界面模块, 胜利界面模块,失败界面模块,游戏界面模块。通过 5 个小模块进行设计,模块 化的设计有利于后续的代码调试,思路也比较的清晰。
3.2.2开始,失败,胜利界面
上电会立即 idle 状态, 此时,ws2812 接口模块准备好,会返回一个 ready 信号,游戏模块接收到 ready 信号,就会进入开始界面,在此界面,会像接口发送 24bit 的数据,让 ws2812 动态显示开始界面,会在屏幕滚动 GAME,在此状态下,按下按键 0,进入游戏模 式,游戏失败或者胜利会进入相应的状态,在相应的状态显示相应的界面,在失 败的界面会滚动显示 LOSE,在胜利的界面会滚动显示 WIN。在这两个状态按下按 键 0,可以结束对应的界面,进入到一个绿色显示的中转界面,在此界面等待 2s, 又重新回到了开始界面,可以进行下一次的游戏。
三个界面会向右滚动显示。
开始界面
失败界面
胜利界面
3.2.3游戏界面
游戏界面介绍:游戏界面需要实现蛇的移动,吃果实增长,随机果实,缠绕 和障碍等功能,把数据输出给游戏控制模块,游戏控制进行总体的控制最后把数 据给显示模块。
蛇的移动:定义方向寄存器位宽为 2,通过四个按键,对应方向寄存器的上 下左右,在定义蛇头时候可以采用一维模式,也可以采用二维模式通过 xy 确定 蛇头的位置,此处我们采用二维,通过一个计数器来控制蛇的移动速度,当方向 寄存器中的值为某个值,就代表某个方向,此时与上计数器的延时就可以达到蛇 的移动。
吃果实并随机生成果实:果实定义位宽 3,形式与蛇头的形式一样,先给果 实一个定值,去判断蛇头的位置是否与食物的位置相等,如果相等,就认为蛇已 经吃到果实,此时在果实的基础上加上一个定值,这样去产生一个假随机的现象, 这样就实现了吃果实并随机生成果实。
增长:蛇的增长可以采用寄存器,先定义蛇的身体,通过移位寄存的方式将 蛇头的位置传递给蛇的身体,在输出的时候去判断蛇头是否等于身体的坐标且此 时的长度为身体的长度,这样就可以正常显示身体。当蛇的身体达到设定值,拉高一个胜利信号,标志游戏成功。 基于 ws2812b 贪吃蛇游戏设计 。
缠绕和障碍物:因为当蛇的头部碰到自己的身体会判断失败,只需要判断蛇 头是否等于身体 4 到 7,从身体 4 判断因为只有身体加头大于 5,才可能咬到自己的身体,障碍物只需要给定一个定值,当蛇头的值与给定的值相等时,就表明撞墙了,此时拉高一个失败信号,标志游戏失败。
3.3ws2812接口模块
ws2812b 接口模块的目的是将接收到的 24RGB 数据发送到 ws2812b 点阵上显示,通过 ws2812b 的时序进行接口设计,在每一次穿数据的时候先要进行复 位,复位之后,进行数据的传输。每次传输 64 个 24bit 的 RGB 数据,由于涉及到多位宽的数据传输,这里需要使用一个 FIFO 来接收控制模块传入的数据,将 数据存储到 FIFO 中。再从FIFO中读取数据的时候需要注意RGB 发送的格式,发送的为 GRB 数据,所以在读取的时候需要进行数据的替换,将 G 和 R 进行替换。 通过 bit 计数器将数据进行并转串进行发送。
4.代码设计
4.1按键消抖代码设计
信号列表
使用状态机来实现按键消抖模块,定义了四个状态,初始状态,按键按下状 态,保持状态,按键松开状态。状态的跳转条件是当检测到下降沿进入按下状态, 延时 20ms 进入保持状态,检测到上升沿进入松开状态,延时 20ms,进入最初始状态。检测到按下通过检测下降沿,来确定是否有按键按下。
按键消抖时序图
按键消抖代码
/**************************************功能介绍***********************************
Date : 2023年7月27日15:14:19
Author : Xlin.
Version : 1.0
Description: 状态机N位按键消抖
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module fsm_filter#(parameter WIDE=1//多少个按键
)(
input clk ,
input rst_n ,
input [WIDE-1:0] key ,
output reg [WIDE-1:0] key_flag
);
//---------<状态机内部信号定义>----------------------------------------------------
parameter IDLE = 4'b0001,
FILTER_DOWN = 4'b0010,
HOLD_DOWN = 4'b0100,
FILTER_UP = 4'b1000;
parameter TIME_20MS=1_000_000;//20ms计数
reg [3:0] state_c;
reg [3:0] state_n;
//---------<状态机转移条件>----------------------------------------------------
wire idle2filter_down ;
wire filter_down2hold_down ;
wire hold_down2filter_up ;
wire filter_up2idle ;
//---------<内部信号及参数定义>----------------------------------------------------
reg [19:0] cnt_20ms;
wire add_cnt_20ms;//开始计数条件
wire end_cnt_20ms;//结束计数条件
wire [WIDE-1:0] n_edge ;//下降沿信号
wire [WIDE-1:0] p_edge ;//上升沿信号
reg [WIDE-1:0] key_r1 ;//打拍信号
reg [WIDE-1:0] key_r2 ;
reg [WIDE-1:0] key_r3 ;
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段:
//状态转移条件
assign idle2filter_down = state_c==IDLE && n_edge;
assign filter_down2hold_down = state_c==FILTER_DOWN && cnt_20ms;
assign hold_down2filter_up = state_c==HOLD_DOWN && p_edge;
assign filter_up2idle = state_c==FILTER_UP && cnt_20ms;
//组合逻辑描述状态转移规律
always @(*) begin
case(state_c)
IDLE : begin
if(idle2filter_down)begin
state_n<=FILTER_DOWN;
end
else
state_n<=state_c;
end
FILTER_DOWN : begin
if(filter_down2hold_down)begin
state_n<=HOLD_DOWN;
end
else
state_n<=state_c;
end
HOLD_DOWN : begin
if(hold_down2filter_up)begin
state_n<=FILTER_UP;
end
else
state_n<=state_c;
end
FILTER_UP : begin
if(filter_up2idle)begin
state_n<=IDLE;
end
else
state_n<=state_c;
end
default : state_n<=IDLE;
endcase
end
//****************************************************************
//--打拍
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
key_r1<={WIDE{1'b1}};
key_r2<={WIDE{1'b1}};
key_r3<={WIDE{1'b1}};
end
else begin
key_r1<=key;
key_r2<=key_r1;
key_r3<=key_r2;
end
end
assign n_edge=~key_r2&key_r3;//下降沿
assign p_edge=~key_r3&key_r2;//上升沿
//****************************************************************
//--20ms计数器
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 'd0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = (state_c==FILTER_DOWN)||(state_c==FILTER_UP);//计数条件
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms ==TIME_20MS-1 ;//记满条件
//第三段:描述输出,时序逻辑或组合逻辑皆可
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
key_flag<='d0;
end
else if(filter_down2hold_down)begin
key_flag<=~key_r3;
end
else
key_flag<='d0;
end
endmodule
4.2游戏控制界面代码设计
信号列表
此模块采用的是状态机进行设计的,一共存在六个状态,初始化,开始界面, 游戏界面,胜利界面,失败界面,等待界面。状态跳转条件,模块接收到 ready 信号开始工作进入开始界面,按下按键,进入游戏界面,游戏胜利进入胜利界面, 游戏失败进入失败界面,在这两个界面按下按键进入等待界面,等待 2s 回到初 始状态,等待下一次的游戏。
/**************************************************************
@File : 贪吃蛇控制模块
@Time : 2023年10月28日11:03:09
@Author : Xlin.
@EditTool: VS Code
@Font : UTF-8
@Function:
**************************************************************/
module game_top (
input clk ,
input rst_n ,
input [3:0] key_flag ,
output reg [23:0] pix_data ,
output reg pix_data_vld ,
input ready //可以接收图像数据了
);
//状态机参数定义
localparam IDLE = 'b000001,
START = 'b000010,
PLAY = 'b000100,
WIN = 'b001000,
LOSE = 'b010000,
DONE = 'b100000;
localparam RED = 24'h110000, //红色
ORANGE = 24'h118000, //橙色
YELLOW = 24'h111100, //黄色
GREEN = 24'h001100, //绿色
CYAN = 24'h001111, //青色
BLUE = 24'h000011, //蓝色
PURPPLE = 24'h800011, //紫色
BLACK = 24'h000000, //黑色
WHITE = 24'h111111, //白色
GRAY = 24'hC0C0C0; //灰色
parameter TIME=100_000_000;//1s
//状态跳转条件定义
wire idle2start;
wire start2play;
wire play2win ;
wire play2lose ;
wire win2done ;
wire lose2done ;
wire done2idle ;
reg [5:0] state_c ;//现态
reg [5:0] state_n ;//次态
//界面显示信号
wire [23:0] start_data;
wire start_data_vld;
wire [23:0] win_data;
wire win_data_vld;
wire [23:0] lose_data;
wire lose_data_vld;
wire [23:0] play_data;
wire play_data_vld;
//标志信号
wire win_falg ;
wire lose_flag;
//每s计数信号
reg [28:0] cnt_delay ;
wire add_cnt_delay ;
wire end_cnt_delay ;
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= START;
end
else begin
state_c <= state_n;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(state_c)
IDLE :if(idle2start)
state_n=START;
else
state_n=state_c;
START:if(start2play)
state_n=PLAY;
else
state_n=state_c;
PLAY :if(play2lose)
state_n=LOSE;
else if(play2win)
state_n=WIN;
else
state_n=state_c;
WIN :if(win2done)
state_n=DONE;
else
state_n=state_c;
LOSE :if(lose2done)
state_n=DONE;
else
state_n=state_c;
DONE :if(done2idle)
state_n=IDLE;
else
state_n=state_c;
default :state_n=IDLE;
endcase
end
assign idle2start=state_c==IDLE && ready;
assign start2play=state_c==START && key_flag[0];//按键0进入游戏
assign play2win =state_c==PLAY && win_falg;
assign play2lose =state_c==PLAY && lose_flag;
assign win2done =state_c==WIN && key_flag[0];
assign lose2done =state_c==LOSE && key_flag[0];
assign done2idle =state_c==DONE && end_cnt_delay;
//****************************************************************
//-- 模块调用
//****************************************************************
//动态显示调用
//开始界面
start game1(
/*input */.clk (clk),
/*input */.rst_n (rst_n),
/*input */.ready (state_c==START),
/*output */.grb_data (start_data),
/*output */.grb_data_vld (start_data_vld)
);
//游戏界面
play game4(
/*input */.clk (clk) ,
/*input */.rst_n (state_c==PLAY) ,
/*input [3:0] */.key_flag (key_flag) ,
/*output reg [23:0] */.pix_data (play_data ) ,
/*output */.pix_data_vld(play_data_vld) ,
/*output */.win_falg (win_falg ) ,
/*output */.lose_flag (lose_flag)
);
//胜利界面
win game2(
/*input */.clk (clk),
/*input */.rst_n (rst_n),
/*input */.ready (state_c==WIN),
/*output */.grb_data (win_data),
/*output */.grb_data_vld (win_data_vld)
);
//失败界面
lose game3(
/*input */.clk (clk),
/*input */.rst_n (rst_n),
/*input */.ready (state_c==LOSE),
/*output */.grb_data (lose_data),
/*output */.grb_data_vld (lose_data_vld)
);
//1s计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 'd0;
end
else if(add_cnt_delay)begin
if(end_cnt_delay)begin
cnt_delay <= 'd0;
end
else begin
cnt_delay <= cnt_delay + 1'b1;
end
end
end
assign add_cnt_delay = state_c==DONE;//一直计数
assign end_cnt_delay = add_cnt_delay && cnt_delay ==TIME-1 ;
//****************************************************************
//-- 数据输出
//****************************************************************
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
pix_data <= CYAN;
end
else
case (state_c)
START:pix_data<=start_data;
PLAY :pix_data<=play_data;
WIN :pix_data<=win_data;
LOSE :pix_data<=lose_data;
DONE :pix_data<=ORANGE;
default: pix_data <= CYAN;
endcase
end
always @(*)begin
case (state_c)
START:pix_data_vld=start_data_vld;
PLAY :pix_data_vld=play_data_vld;
WIN :pix_data_vld=win_data_vld;
LOSE :pix_data_vld=lose_data_vld;
DONE :pix_data_vld=1'b1;
default: pix_data_vld=0;
endcase
end
endmodule
4.2.1游戏开始,失败,胜利代码设计
游戏的开始,失败,胜利界面的设计原理都是一样,通过读取 MIF 里面的数 据,进行动态显示,所以我就选择一个界面进行代码讲解,剩下两个界面是一样 的原理,不同就在于 ROM 里面存的 MIF 文件有所不同,在此首先介绍一下 MIF 文件。 MIF 文件(Memory Initialization File)是一种常用的格式,用于初始化 内存或 FPGA 中的存储器。它包含了实际数据的十六进制表示以及相应地址的信 息。在基于 FPGA 的项目中,MIF 文件常用于初始化程序存储器(如 RAM)或数据 存储器(如 ROM)。MIF 文件由两个主要部分组成:地址(Address)和数据(Data)。
信号列表
ROM ip调取,因为使用到两个寄存起,数据有效信号需要打两拍。
动态显示的数据是从 ROM 里面读出来的,此处需要调取 IP,调取 IP 的教程 我就不叙述了,给定 ROM 读取数据的地址,与读请求信号,就可以从 ROM 里读出 数据。因为我显示的界面是一个 4x32 的照片,要动态显示就需要一个偏移计数 器,偏移的值根据你选的照片进行确定,我要实现的是 x 偏移,所以地址就是 x 的地址加上偏移值,再加上 y 的地址乘 32。
开始界面代码
/**************************************功能介绍***********************************
Date : 2023年11月2日15:16:19
Author : Xlin.
Version :
Description: 贪吃蛇开始界面
*********************************************************************************/
module start (
input clk,
input rst_n,
input ready,
output [23:0] grb_data,
output reg grb_data_vld
);
//内部参数定义
parameter TIME=20_000_000;
//状态机参数定义
localparam S1 = 'b0001,//IDLE
S2 = 'b0010,//DATA
S3 = 'b0100;//DONE
reg [3:0] state_c ;//现态
reg [3:0] state_n ;//次态
reg [4:0] cnt_x ;
wire add_cnt_x ;
wire end_cnt_x ;
reg [4:0] cnt_y ;
wire add_cnt_y ;
wire end_cnt_y ;
wire rd_req;//读数据请求信号
//读信号的打拍信号
reg rd_req_r1;
reg rd_req_r2;
//每帧计数信号
reg [24:0] cnt_delay ;
wire add_cnt_delay ;
wire end_cnt_delay ;
//偏移计数信号
reg [5:0] cnt_offset ;
wire add_cnt_offset ;
wire end_cnt_offset ;
//****************************************************************
//--状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <=S1 ;
end
else begin
state_c <= state_n;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(state_c)
S1 :
if(ready)
state_n=S2;
else
state_n=state_c;
S2 :
if(end_cnt_y)
state_n=S3;
else
state_n=state_c;
S3 :
if(end_cnt_delay)
state_n=S1;
else
state_n=state_c;
default : state_n=S1;
endcase
end
//****************************************************************
//--图像数据计数
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_x <= 'd0;
end
else if(add_cnt_x)begin
if(end_cnt_x)begin
cnt_x <= 'd0;
end
else begin
cnt_x <= cnt_x + 1'b1;
end
end
end
assign add_cnt_x = state_c==S2;
assign end_cnt_x = add_cnt_x && cnt_x ==8-1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_y <= 'd0;
end
else if(add_cnt_y)begin
if(end_cnt_y)begin
cnt_y <= 'd0;
end
else begin
cnt_y <= cnt_y + 1'b1;
end
end
end
assign add_cnt_y = end_cnt_x;
assign end_cnt_y = add_cnt_y && cnt_y ==8-1 ;
//****************************************************************
//--每帧计数器
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 'd0;
end
else if(add_cnt_delay)begin
if(end_cnt_delay)begin
cnt_delay <= 'd0;
end
else begin
cnt_delay <= cnt_delay + 1'b1;
end
end
end
assign add_cnt_delay = state_c == S3;
assign end_cnt_delay = add_cnt_delay && cnt_delay ==TIME-1 ;
//第三段:描述输出,时序逻辑或组合逻辑皆可
//****************************************************************
//--动态图片显示
//****************************************************************
//偏移计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_offset <= 'd0;
end
else if(add_cnt_offset)begin
if(end_cnt_offset)begin
cnt_offset <= 'd0;
end
else begin
cnt_offset <= cnt_offset + 1'b1;
end
end
end
assign add_cnt_offset = end_cnt_delay;
assign end_cnt_offset = add_cnt_offset && cnt_offset ==32-1 ;
wire [4:0] real_row;//防止数据溢出,约束数据,0-31
assign real_row = cnt_x + cnt_offset;
//存放图片
start_rom start_rom_inst (
.aclr ( ~rst_n ),
.address ( real_row + cnt_y*32 ),
.clock ( clk ),
.rden ( rd_req ),
.q ( grb_data )
);
//对读信号进行打拍,因为有两个寄存器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
rd_req_r1 <=0;
rd_req_r2 <=0;
end
else
begin
rd_req_r1 <=rd_req;
rd_req_r2 <=rd_req_r1;
end
end
assign rd_req= state_c== S2;//在DATA状态rom开始读数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
grb_data_vld<= 'd0;
end
else begin
grb_data_vld = rd_req_r2;
end
end
endmodule
失败界面代码
/**************************************功能介绍***********************************
Date : 2023年11月2日15:16:03
Author : Xlin.
Version :
Description: 贪吃蛇失败界面
*********************************************************************************/
module lose (
input clk,
input rst_n,
input ready,
output [23:0] grb_data,
output reg grb_data_vld
);
//内部参数定义
parameter TIME=20_000_000;
//状态机参数定义
localparam S1 = 'b0001,//
S2 = 'b0010,//
S3 = 'b0100,//
S4 = 'b1000;//
reg [3:0] state_c ;//现态
reg [3:0] state_n ;//次态
reg [4:0] cnt_x ;
wire add_cnt_x ;
wire end_cnt_x ;
reg [4:0] cnt_y ;
wire add_cnt_y ;
wire end_cnt_y ;
wire rd_req;//读数据请求信号
wire rd_req_vld;//读数据有效信号
//读信号的打拍信号
reg rd_req_r1;
reg rd_req_r2;
reg rd_req_r3;
//每帧计数信号
reg [24:0] cnt_delay ;
wire add_cnt_delay ;
wire end_cnt_delay ;
//偏移计数信号
reg [5:0] cnt_offset ;
wire add_cnt_offset ;
wire end_cnt_offset ;
//****************************************************************
//--状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <=S1 ;
end
else begin
state_c <= state_n;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(state_c)
S1 :
if(ready)
state_n=S2;
else
state_n=state_c;
S2 :
if(end_cnt_y)
state_n=S3;
else
state_n=state_c;
S3 :
if(end_cnt_delay)
state_n=S1;
else
state_n=state_c;
default : state_n=S1;
endcase
end
//****************************************************************
//--图像数据计数
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_x <= 'd0;
end
else if(add_cnt_x)begin
if(end_cnt_x)begin
cnt_x <= 'd0;
end
else begin
cnt_x <= cnt_x + 1'b1;
end
end
end
assign add_cnt_x = state_c==S2;
assign end_cnt_x = add_cnt_x && cnt_x ==8-1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_y <= 'd0;
end
else if(add_cnt_y)begin
if(end_cnt_y)begin
cnt_y <= 'd0;
end
else begin
cnt_y <= cnt_y + 1'b1;
end
end
end
assign add_cnt_y = end_cnt_x;
assign end_cnt_y = add_cnt_y && cnt_y ==32-1 ;
//****************************************************************
//--每帧计数器
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 'd0;
end
else if(add_cnt_delay)begin
if(end_cnt_delay)begin
cnt_delay <= 'd0;
end
else begin
cnt_delay <= cnt_delay + 1'b1;
end
end
end
assign add_cnt_delay = state_c == S3;
assign end_cnt_delay = add_cnt_delay && cnt_delay ==TIME-1 ;
//第三段:描述输出,时序逻辑或组合逻辑皆可
//****************************************************************
//--动态图片显示
//****************************************************************
//偏移计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_offset <= 'd0;
end
else if(add_cnt_offset)begin
if(end_cnt_offset)begin
cnt_offset <= 'd0;
end
else begin
cnt_offset <= cnt_offset + 1'b1;
end
end
end
assign add_cnt_offset = end_cnt_delay;
assign end_cnt_offset = add_cnt_offset && cnt_offset ==32-1 ;
wire [4:0] real_row;//防止数据溢出,约束数据,0-31
assign real_row = cnt_x + cnt_offset;
//存放图片
lose_rom lose_rom_inst (
.aclr ( ~rst_n ),
.address ( real_row + cnt_y*32 ),
.clock ( clk ),
.rden ( rd_req ),
.q ( grb_data )
);
//对读信号进行打拍,因为有两个寄存器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
rd_req_r1 <=0;
rd_req_r2 <=0;
end
else
begin
rd_req_r1 <=rd_req;
rd_req_r2 <=rd_req_r1;
end
end
assign rd_req= state_c== S2;//在DATA状态rom开始读数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
grb_data_vld<= 'd0;
end
else begin
grb_data_vld = rd_req_r2;
end
end
endmodule
胜利界面代码
/**************************************功能介绍***********************************
Date : 2023年11月2日15:16:35
Author : Xlin.
Version :
Description: 贪吃蛇胜利界面
*********************************************************************************/
module win (
input clk,
input rst_n,
input ready,
output [23:0] grb_data,
output reg grb_data_vld
);
//内部参数定义
parameter TIME=25_000_000;
//状态机参数定义
localparam S1 = 'b0001,//
S2 = 'b0010,//
S3 = 'b0100,//
S4 = 'b1000;//
reg [3:0] state_c ;//现态
reg [3:0] state_n ;//次态
reg [4:0] cnt_x ;
wire add_cnt_x ;
wire end_cnt_x ;
reg [4:0] cnt_y ;
wire add_cnt_y ;
wire end_cnt_y ;
wire rd_req;//读数据请求信号
wire rd_req_vld;//读数据有效信号
//读信号的打拍信号
reg rd_req_r1;
reg rd_req_r2;
reg rd_req_r3;
//每帧计数信号
reg [24:0] cnt_delay ;
wire add_cnt_delay ;
wire end_cnt_delay ;
//偏移计数信号
reg [5:0] cnt_offset ;
wire add_cnt_offset ;
wire end_cnt_offset ;
//****************************************************************
//--状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <=S1 ;
end
else begin
state_c <= state_n;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(state_c)
S1 :
if(ready)
state_n=S2;
else
state_n=state_c;
S2 :
if(end_cnt_y)
state_n=S3;
else
state_n=state_c;
S3 :
if(end_cnt_delay)
state_n=S1;
else
state_n=state_c;
default : state_n=S1;
endcase
end
//****************************************************************
//--图像数据计数
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_x <= 'd0;
end
else if(add_cnt_x)begin
if(end_cnt_x)begin
cnt_x <= 'd0;
end
else begin
cnt_x <= cnt_x + 1'b1;
end
end
end
assign add_cnt_x = state_c==S2;
assign end_cnt_x = add_cnt_x && cnt_x ==8-1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_y <= 'd0;
end
else if(add_cnt_y)begin
if(end_cnt_y)begin
cnt_y <= 'd0;
end
else begin
cnt_y <= cnt_y + 1'b1;
end
end
end
assign add_cnt_y = end_cnt_x;
assign end_cnt_y = add_cnt_y && cnt_y ==8-1 ;
//****************************************************************
//--每帧计数器
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 'd0;
end
else if(add_cnt_delay)begin
if(end_cnt_delay)begin
cnt_delay <= 'd0;
end
else begin
cnt_delay <= cnt_delay + 1'b1;
end
end
end
assign add_cnt_delay = state_c == S3;
assign end_cnt_delay = add_cnt_delay && cnt_delay ==TIME-1 ;
//第三段:描述输出,时序逻辑或组合逻辑皆可
//****************************************************************
//--动态图片显示
//****************************************************************
//偏移计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_offset <= 'd0;
end
else if(add_cnt_offset)begin
if(end_cnt_offset)begin
cnt_offset <= 'd0;
end
else begin
cnt_offset <= cnt_offset + 1'b1;
end
end
end
assign add_cnt_offset = end_cnt_delay;
assign end_cnt_offset = add_cnt_offset && cnt_offset ==32-1 ;
wire [4:0] real_row;//防止数据溢出,约束数据,0-31
assign real_row = cnt_x + cnt_offset;
//存放图片
win_rom win_rom_inst (
.aclr ( ~rst_n ),
.address ( real_row + cnt_y*32 ),
.clock ( clk ),
.rden ( rd_req ),
.q ( grb_data )
);
//对读信号进行打拍,因为有两个寄存器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
rd_req_r1 <=0;
rd_req_r2 <=0;
end
else
begin
rd_req_r1 <=rd_req;
rd_req_r2 <=rd_req_r1;
end
end
assign rd_req= state_c== S2;//在DATA状态rom开始读数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
grb_data_vld<= 'd0;
end
else begin
grb_data_vld = rd_req_r2;
end
end
endmodule
4.2.2游戏代码设计
信号列表
蛇头的方向控制,通过方向寄存器进行控制,四个按键控制上下左右,在控制上下移动的时候,按键上生效的条件就是当前的按键按下,且上一次方向不为 相反的方向,通过 delay 计数器控制蛇的移速,和不按按键的时候保持原方向移动。
蛇头移动时序图
蛇身代码设计,根据移位寄存器的方法,将蛇头的位置给蛇身,这样就实现 了身体跟着头一起移动的效果。
蛇吃食物增加长度,和随机刷新果实,定义一个长度信号,当蛇头的地址食 物的地址相同时候,就认为蛇吃到了食物,此时长度加一,在吃到食物之后,果 实的地址加一个定值,这样就实现了随机果实的产生,注意使用这个方式果实的 位宽必须限制在 3 位,最大值不能超过 8,位宽超过 8 以后可能会出现蛇头吃下 食物之后,产生的食物的地址不在点阵上。食物判断是否产生在墙上或者障碍物 上,如果在墙上或者障碍物上就要重新生成食物。
判断是否缠绕,撞墙,撞障碍物,原理都是一样的,去判断蛇头是否在这些 位置上,如果在这些位置上,就拉高失败信号,标志着游戏失败。
数据的输出,去判断位置,通过点阵的 x,y 计数器,判断计数器的值等于 蛇头 x 和蛇头 y 的值,输出 BLUE,蛇头为蓝色,蛇的身体,通蛇头一样去判断 地址是否相等还要与上蛇的长度,两个都条件满足,输出 RED,身体为红色,障 碍物和墙都是一样的判断。
蛇身体时序图
果实生成时序图
撞墙,撞障碍物判断时序图
蛇长度判断时序图
游戏代码
/**************************************功能介绍***********************************
Date : 2023年11月2日10:14:43
Author : Xlin.
Version :
Description: 贪吃蛇游戏界面
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module play (
input clk ,
input rst_n ,
input [3:0] key_flag ,
output reg [23:0] pix_data ,
output reg pix_data_vld ,
output win_falg ,
output lose_flag
);
//---------<参数定义>---------------------------------------------------------
localparam RED = 24'hff0000, //红色
ORANGE = 24'hff8000, //橙色
YELLOW = 24'hffff00, //黄色
GREEN = 24'h00ff00, //绿色
CYAN = 24'h00ffff, //青色
BLUE = 24'h0000ff, //蓝色
PURPPLE = 24'h8000ff, //紫色
BLACK = 24'h000000, //黑色
WHITE = 24'h111111, //白色
GRAY = 24'hC0C0C0; //灰色
//动态显示时间
parameter TIME=50_000_000;//1s
//---------<内部信号定义>-----------------------------------------------------
//行计数
reg [5:0] cnt_x;
wire add_x_cnt;
wire end_x_cnt;
//列计数
reg [4:0] cnt_y;
wire add_y_cnt;
wire end_y_cnt;
//每帧计数信号
reg [25:0] cnt_delay ;
wire add_cnt_delay ;
wire end_cnt_delay ;
//方向寄存器
reg [1:0] direction;
//蛇头坐标寄存器
reg [2:0] snake_head_x;
reg [2:0] snake_head_y;
//蛇尾坐标寄存器
reg [5:0] snake_tail_1;
reg [5:0] snake_tail_2;
reg [5:0] snake_tail_3;
reg [5:0] snake_tail_4;
reg [5:0] snake_tail_5;
reg [5:0] snake_tail_6;
reg [5:0] snake_tail_7;
//食物寄存器
reg [2:0] food_x;
reg [2:0] food_y;
//蛇头数据有效
reg flag ;
reg play_vlg;
//蛇长度
reg [3:0] length;
//失败信号
reg lose_flag1;
//****************************************************************
//-- PLAY模块设计
//****************************************************************
//方向控制 01左右 23上下
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
direction <= 'd0;
end
else if(key_flag[0] && direction!=1) begin//左
direction <= 'd0;
end
else if(key_flag[1] && direction!=0) begin//有
direction <= 'd1;
end
else if(key_flag[2] && direction!=3) begin//上
direction <= 'd2;
end
else if(key_flag[3] && direction!=2) begin//下
direction <= 'd3;
end
else begin
direction <= direction;
end
end
//蛇头横坐标控制 end_cnt_dealy的作用是控制蛇的移速
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
snake_head_x<= 'd3;
end
else if(direction=='d0 && end_cnt_delay )begin
snake_head_x<=snake_head_x - 1'd1;
end
else if(direction=='d1 && end_cnt_delay )begin
snake_head_x<=snake_head_x + 1'd1;
end
end
//蛇头纵坐标控制
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
snake_head_y<= 'd2;
end
else if(direction=='d2 && end_cnt_delay)begin
snake_head_y<=snake_head_y - 1'd1;
end
else if(direction=='d3 && end_cnt_delay)begin
snake_head_y<=snake_head_y + 1'd1;
end
end
//蛇身体控制
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
snake_tail_1 <= 0;
snake_tail_2 <= 0;
snake_tail_3 <= 0;
snake_tail_4 <= 0;
snake_tail_5 <= 0;
snake_tail_6 <= 0;
snake_tail_7 <= 0;
end
else if(end_cnt_delay)begin
snake_tail_1 <= snake_head_x + snake_head_y*8 ;
snake_tail_2 <= snake_tail_1;
snake_tail_3 <= snake_tail_2;
snake_tail_4 <= snake_tail_3;
snake_tail_5 <= snake_tail_4;
snake_tail_6 <= snake_tail_5;
snake_tail_7 <= snake_tail_6;
end
end
//吃果实,吃完之后加一个数,模拟产生随机果实。
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
food_x <= 'd5;
food_y <= 'd5;
end
else if((snake_head_x == food_x) && (snake_head_y == food_y))begin
food_x <= food_x+ 'd10 ;
food_y <= food_y+ 'd10 ;
end
else if((food_x<=7 && food_y==0)||(food_x<=7 && food_y==7)||(food_y<=7 && food_x==0)||(food_y<=7 && food_x==0))begin//判断果实是否在生在墙上,是重新生成果实。
food_x <= food_x+ 'd10 ;
food_y <= food_y+ 'd10 ;
end
else if(food_x+food_y*8==21||food_x+food_y*8==22)begin
food_x <= food_x+ 'd10 ;
food_y <= food_y+ 'd10 ;
end
//吃果实增长
always@(posedge clk or negedge rst_n)
if(!rst_n)
length <= 'd0;
else if((snake_head_x == food_x) && (snake_head_y == food_y))
length <= length + 'd1;
else
length <= length;
//判断是否撞墙或者是撞身体
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
lose_flag1<= 'd0;
end
else if((snake_head_x + snake_head_y*8==snake_tail_3 )&& length==3)//判断是否撞身体
lose_flag1 <= 'd1;
else if((snake_head_x + snake_head_y*8==snake_tail_4) && length==4)
lose_flag1 <= 'd1;
else if((snake_head_x + snake_head_y*8==snake_tail_5) && length==5)
lose_flag1 <= 'd1;
else if((snake_head_x + snake_head_y*8==snake_tail_6) && length==6)
lose_flag1 <= 'd1;
else if((snake_head_x + snake_head_y*8==snake_tail_7) && length==7)
lose_flag1 <= 'd1;
else if(snake_head_x <=7 && snake_head_y==0)//上墙
lose_flag1 <= 'd1;
else if(snake_head_x <=7 && snake_head_y==7)//下墙
lose_flag1 <= 'd1;
else if(snake_head_y <=7 && snake_head_x==0)//左墙
lose_flag1 <= 'd1;
else if(snake_head_y <=7 && snake_head_x==7)//右墙
lose_flag1 <= 'd1;
else if(snake_head_x + snake_head_y*8=='d21)//障碍物
lose_flag1 <= 'd1;
else if(snake_head_x + snake_head_y*8=='d22)//障碍物
lose_flag1 <= 'd1;
else
lose_flag1 <= 'd0;
end
//数据有效信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag<= 'd0;
end
else if(end_cnt_delay)begin
flag<=1'd1;
end
else if(end_y_cnt)begin
flag<=1'd0;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
play_vlg<= 'd0;
pix_data_vld<='d0;
end
else begin
play_vlg<=add_x_cnt;
pix_data_vld<=play_vlg;
end
end
/**************************************************************
显示
**************************************************************/
//1s计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 'd0;
end
else if(add_cnt_delay)begin
if(end_cnt_delay)begin
cnt_delay <= 'd0;
end
else begin
cnt_delay <= cnt_delay + 1'b1;
end
end
end
assign add_cnt_delay = 1'd1;//一直计数
assign end_cnt_delay = add_cnt_delay && cnt_delay ==TIME-1 ;
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_x <= 'd0;
else if(add_x_cnt) begin
if(end_x_cnt)
cnt_x <= 'd0;
else
cnt_x <= cnt_x + 1'b1;
end
assign add_x_cnt = flag ;
assign end_x_cnt = add_x_cnt && cnt_x == 'd8 - 1'd1;
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_y <= 'd0;
else if(add_y_cnt) begin
if(end_y_cnt)
cnt_y <= 'd0;
else
cnt_y <= cnt_y + 1'b1;
end
assign add_y_cnt = end_x_cnt;
assign end_y_cnt = add_y_cnt && cnt_y == 'd8 - 1'd1;
//****************************************************************
//-- 数据输出
//****************************************************************
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
pix_data <= WHITE;
end
else if(cnt_x == snake_head_x && cnt_y == snake_head_y) //蛇头
pix_data <= BLUE;
else if(cnt_x + cnt_y*8 == snake_tail_1 && length >= 1)//蛇身
pix_data <= RED;
else if(cnt_x + cnt_y*8 == snake_tail_2 && length >= 2)
pix_data <= RED;
else if(cnt_x + cnt_y*8 == snake_tail_3 && length >= 3)
pix_data <= RED;
else if(cnt_x + cnt_y*8 == snake_tail_4 && length >= 4)
pix_data <= RED;
else if(cnt_x + cnt_y*8 == snake_tail_5 && length >= 5)
pix_data <= RED;
else if(cnt_x + cnt_y*8 == snake_tail_6 && length >= 6)
pix_data <= RED;
else if(cnt_x + cnt_y*8 == snake_tail_7 && length >= 7)
pix_data <= RED;
else if(cnt_x == food_x && cnt_y == food_y)//食物
pix_data <= GREEN;
else if(cnt_x<=7 && cnt_y ==0)//四面墙
pix_data <= CYAN;
else if(cnt_x<=7 && cnt_y ==7)
pix_data <= CYAN;
else if(cnt_y<=7 && cnt_x ==0)
pix_data <= CYAN;
else if(cnt_y<=7 && cnt_x ==7)
pix_data <= CYAN;
else if(cnt_x + cnt_y*8 =='d21)//障碍物
pix_data <=YELLOW;
else if(cnt_x + cnt_y*8 =='d22)//障碍物
pix_data <=YELLOW;
else
pix_data <= WHITE;
end
//判断是否胜利或者失败
assign win_falg=length==6?1'b1:1'b0;
assign lose_flag=lose_flag1?1'b1:1'b0;
endmodule
4.3ws2812接口代码设计
阅读 ws2812b 手册,了解产生 0 码和 1 码的时序,根据时序去设计程序,通 过手册我们可以知道,在每传送一帧数据之后,传输下一帧数据的时候,需要对 点阵进行复位,由此我们可以设计一个状态机,在每传送数据的时候都进行复位 一次,先确定接口模块的信号。
信号如表
接口模块时序图
接口代码
/**************************************************************
@File : ws2812_drive.v
@Time : 2023年10月28日08:52:06
@Author : Xlin.
@EditTool: VS Code
@Font : UTF-8
@Function: ws2818接口控制器,将接收到到的24rgb数据发送到ws2812器件显示
一个bit的周期1200ns,时钟50M,一共60个时钟周期
0码 高电平 300ns
低电平 900ns
1码 高电平 600ns
低电平 600ns
**************************************************************/
module ws2812_drive (
input clk ,
input rst_n ,
input [23:0] pix_data ,
input pix_data_vld ,
output ready , //可以接收图像数据了
output reg ws2812_io
);
parameter IDLE = 0,
RST = 1,
DATA = 2;
localparam T0H = 300/20 ,
T0L = 900/20 ,
T1H = 600/20 ,
T1L = 600/20 ;
parameter RST_MAX = 400_000/20;
reg [1:0] state ;
wire idle2rst ;
wire rst2data ;
wire data2idle ;
wire fifo_wr_req ;
wire fifo_rd_req ;
wire [23:0] fifo_wr_data ;
wire [23:0] fifo_rd_data ;
wire fifo_empty ;
wire fifo_full ;
//01持续时间计数器
reg [11:0] cnt_cyc;
wire add_cyc_cnt;
wire end_cyc_cnt;
//bit计数器(rgb24bit)
reg [4:0] cnt_bit;
wire add_bit_cnt;
wire end_bit_cnt;
//64点阵计数器
reg [5:0] cnt_num;
wire add_num_cnt;
wire end_num_cnt;
//复位计数器
reg [31:0] cnt_rst;
wire add_rst_cnt;
wire end_rst_cnt;
/**************************************************************
状态机
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else case(state)
IDLE : if(idle2rst)
state <= RST;
RST : if(rst2data)
state <= DATA;
DATA : if(data2idle)
state <= IDLE;
default : state <= IDLE;
endcase
assign idle2rst = state == IDLE && pix_data_vld; //已经存放了一帧图像数据了
assign rst2data = state == RST && end_rst_cnt; //400us复位时间结束
assign data2idle = state == DATA && end_num_cnt;
/**************************************************************
缓存图像数据
**************************************************************/
fifo fifo_inst (
.aclr (~rst_n ),
.clock (clk ),
.data (fifo_wr_data ), //24bit GRB
.rdreq (fifo_rd_req ),
.wrreq (fifo_wr_req ),
.empty (fifo_empty ),
.full (fifo_full ),
.q (fifo_rd_data ), //24bit GRB
.usedw ( )
);
assign fifo_wr_data = {pix_data[15:8],pix_data[23:16],pix_data[7:0]};
assign fifo_wr_req = pix_data_vld && ~fifo_full;
assign fifo_rd_req = end_bit_cnt && ~fifo_empty;
/**************************************************************
数据时间线控制
**************************************************************/
//一共64*24*1200ns
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_cyc <= 'd0;
else if(add_cyc_cnt) begin
if(end_cyc_cnt)
cnt_cyc <= 'd0;
else
cnt_cyc <= cnt_cyc + 1'b1;
end
assign add_cyc_cnt = state == DATA;
assign end_cyc_cnt = add_cyc_cnt && cnt_cyc == 1200/20 - 1; //一个bit的持续时间
//****************************************************************
//-- rgb数据计数器
//****************************************************************
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_bit <= 'd0;
else if(add_bit_cnt) begin
if(end_bit_cnt)
cnt_bit <= 'd0;
else
cnt_bit <= cnt_bit + 1'b1;
end
assign add_bit_cnt = end_cyc_cnt;
assign end_bit_cnt = add_bit_cnt && cnt_bit == 24 - 1;
//****************************************************************
//-- 8x8点阵计数器
//****************************************************************
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_num <= 'd0;
else if(add_num_cnt) begin
if(end_num_cnt)
cnt_num <= 'd0;
else
cnt_num <= cnt_num + 1'b1;
end
assign add_num_cnt = end_bit_cnt;
assign end_num_cnt = add_num_cnt && cnt_num == 64 - 1;
/**************************************************************
复位计数器
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_rst <= 'd0;
else if(add_rst_cnt) begin
if(end_rst_cnt)
cnt_rst <= 'd0;
else
cnt_rst <= cnt_rst + 1'b1;
end
assign add_rst_cnt = state == RST;
assign end_rst_cnt = add_rst_cnt && cnt_rst == RST_MAX - 1; //复位时间400us
/**************************************************************
实现单总线协议
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
ws2812_io <= 1;
else case(state)
IDLE : ws2812_io <= 1;
RST : ws2812_io <= 0;
DATA : if(fifo_rd_data[23-cnt_bit]) begin //发送数据1
if(cnt_cyc < T1H)
ws2812_io <= 1;
else
ws2812_io <= 0;
end
else begin //发送数据0
if(cnt_cyc < T0H)
ws2812_io <= 1;
else
ws2812_io <= 0;
end
default : ws2812_io <= 1;
endcase
assign ready = state == IDLE;
endmodule
3.4顶层模块代码
/**************************************功能介绍***********************************
Date : 2023年11月1日13:25:58
Author : Xlin.
Version : 1.0
Description: 顶层模块
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module top(
input clk ,
input rst_n ,
input [3:0] key ,
output ws2812_io
);
//---------<参数定义>---------------------------------------------------------
wire [3:0] key_flag ;
wire [23:0] pix_data ;
wire pix_data_vld;
wire ready ;
//---------<内部信号定义>-----------------------------------------------------
//按键模块列化
fsm_filter#(.WIDE(4))
top_fsm_key(
/*input */.clk (clk ),
/*input */.rst_n (rst_n),
/*input [WIDE-1:0] */.key (key ),
/*output reg [WIDE-1:0] */.key_flag (key_flag)
);
//控制模块列化
game_top topgame(
/*input */.clk (clk ) ,
/*input */.rst_n (rst_n) ,
/*input [3:0] */.key_flag (key_flag ),
/*output reg [23:0] */.pix_data (pix_data ),
/*output reg */.pix_data_vld(pix_data_vld),
/*input */.ready (ready ) //可以接收图像数据了
);
//ws2812模块列化
ws2812_drive topws2812(
/*input */.clk (clk ),
/*input */.rst_n (rst_n),
/*input [23:0] */.pix_data (pix_data),
/*input */.pix_data_vld(pix_data_vld),
/*output */.ready (ready), //可以接收图像数据了
/*output reg */.ws2812_io (ws2812_io)
);
endmodule
演示视频
贪吃蛇游戏演示视频
工程原码如有所需,自行下载,遇到bug,欢迎你告诉我,谢谢!