项目需求
使用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 X、Windows和 Linux 之上的,针对于编写现代Web和云应用的跨平台源代码编辑器,可在桌面上运行,并且可用于Windows,macOS和Linux。它具有对JavaScript,TypeScript和Node.js的内置支持,并具有丰富的其他语言(例如C++,C#,Java,Python,PHP,Go)和运行时(例如.NET和Unity)扩展的生态系统。
在本项目中主要用于代码编写。
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。