基于小脚丫FPGA套件STEP BaseBoard V4.0制作像素风格贪吃蛇小游戏
该项目使用了step baseboard v4.0平台,lattice mxo2芯片,lattice diamond软件,verilog语言,实现了像素风贪吃蛇小游戏的设计,它的主要功能为:利用adxl345姿态传感器和4x4矩阵键盘,操控小蛇吃蛋糕块,将图像显示的tft-lcd上。
标签
FPGA
Funpack活动
显示
开发板
180cly
更新2024-04-01
山东农业大学
307

项目需求

使用FPGA逻辑来设计一款游戏,能够利用LCD以及HDMI驱动显示器(切换使用),并通过按键和姿态传感器来控制游戏的进程。

软硬件平台

1.Lattice DIamond:

莱迪思Diamond是一款先进的设计软件,专为通用FPGA系列的设计探索而优化,可以在单个项目中进行压力测试和评估多个解决方案。Diamond为莱迪思FPGA提供优化、量身定制的设计和验证环境,具有高级优化、精准分析、广泛验证和快速迭代等特性。

2.modelsim:

Mentor公司的ModelSim是业界最优秀的HDL语言仿真软件,它能提供友好的仿真环境,是业界唯一的单内核支持VHDL和Verilog混合仿真仿真器。它采用直接优化的编译技术Tcl/Tk技术、和单一内核仿真技术,编译仿真速度快,编译的代码与平台无关,便于保护IP核,个性化的图形界面用户接口,为用户加快调错提供强有力的手段,是FPGA/ASIC设计的首选仿真软件。(百度百科)

在本项目中主要用于对工程进行模拟仿真。

3.Visual Studio Code:

Visual Studio Code是Microsoft在2015年4月30日Build开发者大会上正式宣布一个运行于 Mac OS XWindows Linux 之上的,针对于编写现代Web云应用的跨平台源代码编辑器,可在桌面上运行,并且可用于WindowsmacOSLinux。它具有对JavaScriptTypeScriptNode.js的内置支持,并具有丰富的其他语言(例如C++C#JavaPythonPHPGo)和运行时(例如.NETUnity)扩展的生态系统

在本项目中主要用于代码编写。

4.LCMXO2-4000HC-5MG132C:

LCMXO2-4000HC-5MG132C是由Lattice公司开发的一款低功耗FPGA芯片,搭载于本设计中小脚丫团队最新推出的FPGA核心板,在本项目中用于对4x4矩阵键盘、adxl345,复位按键等的信号进行处理,并生成游戏的图像,驱动st7786芯片来控制tft—lcd。

5.STEP BaseBoard V4.0

一款搭载丰富外设的开发平台,在本设计中使用到其中的adxl345,tft—lcd,4x4矩阵按键。


功能框图

设计思路

1.采集姿态传感器和矩阵按键的信号,并通过swi开关来判断使用哪个信号控制小蛇的运动。

parameter   X_THRESHOLD = 11'd100;
parameter   Y_THRESHOLD = 11'd100;

//负数
reg x_nega;
reg y_nega;
//溢出
reg x_over;
reg y_over;

reg [3:0]   key_dat;
reg [3:0]   code_out;

//x_dat/y_dat[12]为1,输入为负数
//此时atti_sig[3],atti_sig[1]为1
//x_dat,y_dat超出阀值时atti_sig[2],atti_sig[0]为1
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        x_nega <= 1'b0;
    end
    else if (x_data[12])    begin
        x_nega <= 1'b1;
    end
end

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        y_nega <= 1'b0;
    end
    else if (y_data[12])    begin
        y_nega <= 1'b1;
    end
end

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        x_over <= 1'b0;
    end
    else    begin
        x_over <= (x_data[11:0] > X_THRESHOLD)?1'b1:1'b0;
    end
end

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        y_over <= 1'b0;
    end
    else    begin
        y_over <= (y_data[11:0] > Y_THRESHOLD)?1'b1:1'b0;
    end
end

//矩阵键盘
//2上
//10下
//5左
//7右
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        key_dat <= 4'b0;
    end
    else    begin
        case (key_pulse)
            16'h0002:key_dat <= 4'b0001;
            16'h0200:key_dat <= 4'b0011;
            16'h0010:key_dat <= 4'b1100;            
            16'h0040:key_dat <= 4'b0100;            
            default: key_dat <= key_dat;
        endcase
    end
end

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        code_out <= 4'b0;
    end
    else    begin
        if (swi_in) begin
            code_out <= {x_data[12],x_over,y_data[12],y_over};
        end
        else begin
            code_out <= key_dat;
        end
    end
end

assign atti_sig = code_out;

2.使用计数器计时,时间到了就对开始对小蛇运动状态和位置刷新。

//状态机
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        state_c <= ST0;
    end
    else    begin
        case (state_c)
            ST0  :begin
                // state_c <= (flag_100us  )?ST1:state_c;
                //仿真
                state_c <= (flag_renew  )?ST1:state_c;//等待更新
            end
            ST1  :begin
                state_c <= (inertia_flag)?ST2:state_c;//获取姿态
            end
            ST2  :begin
                state_c <= (head_flag   )?JUDGE:state_c;//蛇头位置更新
            end
            JUDGE:begin
                state_c <= ST3;//检测是否吃到蛋糕并更新蛇的长度
            end
            ST3  :begin
                state_c <= (posi_flag   )?ST4:state_c;//蛇身位置更新
            end
            ST4  :begin
                state_c <= (cake_flag   )?DONE:state_c;//检测蛋糕并对其刷新
            end
            default: state_c <= ST0;
        endcase
    end
end

计数器中的一个

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        flag_renew <= 1'b0;
        cnt_renew <= 10'd0;
    end
    else    begin
        if(state_c == ST0)begin
            if (flag_10ms) begin
                if (cnt_renew == RENEW_NUM) begin
                    flag_renew <= 1'b1;
                    cnt_renew <= 10'd0;
                end
                else begin
                    flag_renew <= 1'b0;
                    cnt_renew <= cnt_renew + 1'b1;
                end
            end
            else begin
                flag_renew <= 1'b0;
                cnt_renew <= cnt_renew;
            end
        end
        else begin
            flag_renew <= 1'b0;
            cnt_renew <= 10'd0;
        end
    end
end

根据上一个周期小蛇运动方向和输入的方向信号判断下一个周期的运动方向

if(state_c == ST1)begin
            inertia_flag <= 1'b1;       //inertia_flag set
            case (snake_inertia)
                4'b1000:begin
                    case (atti_sig)
                        4'b1100,4'b1101,4'b1110,4'b1111:begin
                            snake_inertia <= 4'b0010;//向左
                        end
                        4'b0100,4'b0101,4'b0110,4'b0111:begin
                            snake_inertia <= 4'b0001;//向右
                        end
                        4'b0001,4'b1001:begin
                            snake_inertia <= 4'b1000;//向上
                        end
                        4'b0011,4'b1011 :begin
                            // snake_inertia <= 4'b0100;//向下
                            snake_inertia <= snake_inertia;//方向相反了转不回去
                        end
                        default: snake_inertia <= snake_inertia;
                    endcase
                end

对蛇头位置更新

 if((state_c == ST2)&&(head_flag == 1'b0))begin
            head_flag <= 1'b1;
            snake_head <= snake_posi[0];
            case (snake_inertia)
                4'b1000:begin
                    snake_posi[0][3:0] <= (snake_posi[0][3:0] == 4'b0)?4'b1100:snake_posi[0][3:0] - 1'b1;
                end
                4'b0100:begin
                    snake_posi[0][3:0] <= (snake_posi[0][3:0] == 4'b1100)?4'b0:snake_posi[0][3:0] + 1'b1;
                end
                4'b0010:begin
                    snake_posi[0][7:4] <= (snake_posi[0][7:4] == 4'b0)?4'b1111:snake_posi[0][7:4] - 1'b1;//向左
                end
                4'b0001:begin
                    snake_posi[0][7:4] <= (snake_posi[0][7:4] == 4'b1111)?2'b0:snake_posi[0][7:4] + 1'b1;//向右
                end
                default: snake_posi[0] <= snake_posi[0];
            endcase
        end

检测是否撞到蛋糕

else    begin
        if(state_c == JUDGE)begin
            if(cake_posi == snake_posi[0][7:0])begin
                snake_len <= snake_len + 1'b1;          //蛇头与蛋糕相撞,len+1
            end
        end
    end

对小蛇的其它部分更新,设置了8个寄存器来存储位置数据和1个寄存器存蛋糕位置

snake_posi[1][7:0] <= snake_head;
            snake_posi[2][7:0] <= snake_posi[1][7:0];
            snake_posi[3][7:0] <= snake_posi[2][7:0];
            snake_posi[4][7:0] <= snake_posi[3][7:0];
            snake_posi[5][7:0] <= snake_posi[4][7:0];
            snake_posi[6][7:0] <= snake_posi[5][7:0];
            snake_posi[7][7:0] <= snake_posi[6][7:0];

最后查看是否需要更新蛋糕位置

case (snake_len)
                3'd1    :   cake_posi <= 8'b0110_0011;//(6,3)
                3'd2    :   cake_posi <= 8'b0011_0110;//(3,6)
                3'd3    :   cake_posi <= 8'b1011_0010;//(11,2)
                3'd4    :   cake_posi <= 8'b0111_1010;//(7,10)
                3'd5    :   cake_posi <= 8'b0001_0010;//(1,2)
                3'd6    :   cake_posi <= 8'b1000_1011;//(8,11)
                3'd7    :   cake_posi <= 8'b0010_0110;//(2,6)        
                // 3'd71    :   cake_posi <= 7'b0000_0000;
                default:    cake_posi <= 8'b1100_1000;
            endcase

3.将刷新后的信号传送给snake_graphic模块,生成图像数据。

使用一个192位寄存器存储图像信息

else    begin
        case (state_c)
            STATE0:    state_c <= (draw_en)?STATE1:STATE0;
            STATE1:    state_c <= (state1_finish_flag)?STATE2:STATE1;
            STATE2:    state_c <= (cnt_map_f)?STATE3:STATE2;
            STATE3:    state_c <= (address == 9'd319)?DONE:STATE3;
            DONE  :    state_c <= STATE0;
            default: state_c <= STATE0;
        endcase
    end

算出9个寄存器中的位置信息(设置了8个寄存器来存储位置数据和1个寄存器存蛋糕位置)

case (cnt_mult)
            4'd0:begin
                temp0 <= snake0[7:4];
                temp1 <= snake0[3:0];
            end
            4'd1 :begin
                temp0 <= snake1[7:4];
                temp1 <= snake1[3:0];
                mult0 <= 16*temp1 + temp0;
            end
            4'd2 :begin
                temp0 <= snake2[7:4];
                temp1 <= snake2[3:0];
                mult1 <= 16*temp1 + temp0;
            end
            4'd3 :begin
                temp0 <= snake3[7:4];
                temp1 <= snake3[3:0];
                mult2 <= 16*temp1 + temp0;
            end
            4'd4 :begin
                temp0 <= snake4[7:4];
                temp1 <= snake4[3:0];
                mult3 <= 16*temp1 + temp0;
            end
......

根据算出的信息对192位寄存器赋值

case (cnt_map)
            4'd0 :begin
                sm_addr <= mult0;
                en_sm <= snake0[8];
            end
            4'd1 :begin
                sm_addr <= mult1;
                en_sm <= snake1[8];
            end
            4'd2 :begin
                sm_addr <= mult2;
                en_sm <= snake2[8];
            end
            4'd3 :begin
                sm_addr <= mult3;
                en_sm <= snake3[8];
            end
....

根据输入的地址输出图像数据

case (address)
            9'd0    :ram0 <= {screen_map[0],screen_map[16],screen_map[32],screen_map[48],screen_map[64],screen_map[80],screen_map[96],screen_map[112],screen_map[128],screen_map[144],screen_map[160],screen_map[176]};            
            9'd20   :ram0 <= {screen_map[1],screen_map[17],screen_map[33],screen_map[49],screen_map[65],screen_map[81],screen_map[97],screen_map[113],screen_map[129],screen_map[145],screen_map[161],screen_map[177]};
            9'd40   :ram0 <= {screen_map[2],screen_map[18],screen_map[34],screen_map[50],screen_map[66],screen_map[82],screen_map[98],screen_map[114],screen_map[130],screen_map[146],screen_map[162],screen_map[178]};
            9'd60   :ram0 <= {screen_map[3],screen_map[19],screen_map[35],screen_map[51],screen_map[67],screen_map[83],screen_map[99],screen_map[115],screen_map[131],screen_map[147],screen_map[163],screen_map[179]};
            9'd80   :ram0 <= {screen_map[4],screen_map[20],screen_map[36],screen_map[52],screen_map[68],screen_map[84],screen_map[100],screen_map[116],screen_map[132],screen_map[148],screen_map[164],screen_map[180]};
            9'd100  :ram0 <= {screen_map[5],screen_map[21],screen_map[37],screen_map[53],screen_map[69],screen_map[85],screen_map[101],screen_map[117],screen_map[133],screen_map[149],screen_map[165],screen_map[181]};
.....
graphic_r[19:0] <= {ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0],ram0[0]};
        graphic_r[39:20] <= {ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1],ram0[1]};
        graphic_r[59:40] <= {ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2],ram0[2]};
        graphic_r[79:60] <= {ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3],ram0[3]};
        graphic_r[99:80] <= {ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4],ram0[4]};
....

4.利用修改后的lab13_picture_display(官方demo)将图像数据和命令通过spi发送给st7789,并由后者控制lcd屏幕。


FPGA资源占用

实物效果图


部分模块的仿真

snake_graphic模块

snake模块

仿真时状态机处代码有修改

attitude_code模块

其余部分模块源于官方demo,非常的可靠,故没有做仿真。

使用到的官方demo有lab1_type_system、lab11_accelerometer、lab13_picture_display。


附件下载
snake.zip
团队介绍
陈林阳,山东农业大学
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号