基于ws2812贪吃蛇游戏设计

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,欢迎你告诉我,谢谢!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于WS2812的贪吃游戏需要使用Verilog语言进行设计。首先,需要了解WS2812是一种RGB LED驱动芯片,可实现每个像素独立控制颜色和亮度的特性。 设计思路是将WS2812组成一个2D网格,每个像素代表的一节身体或食物。首先,需要实现的移动逻辑,可以使用一个包含身坐标的链表数据结构来记录的位置信息。当吃到食物时,链表会增加一个新的节点,表示的身体增长。 接下来,需要处理用户输入。可以通过外部IO接口,例如按键或开关来控制的移动方向。根据用户输入,更新头的位置和链表中的节点顺序。 同时,还需要处理与食物之间的碰撞检测。如果头与食物位置重合,表示吃到了食物,将食物节点添加到链表中,并生成一个新的随机食物位置。 另外,还需要处理与自身身体的碰撞检测。如果头与身碰撞,表示游戏结束,可以根据设计需求,输出游戏结束信号或清空链表数据。 最后,需要将链表中的节点信息映射到WS2812上,实现和食物的显示效果。可以使用Verilog中的定时器和状态机来控制WS2812的RGB值和亮度,实现动画效果。 总结起来,基于WS2812的贪吃游戏的Verilog设计主要包括的移动逻辑、用户输入处理、碰撞检测以及WS2812的控制和显示效果。通过合理的设计和编程,可以实现一个动态、有趣的贪吃游戏

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值