当前位置:   article > 正文

【友晶科技】基于FPGA的贪吃蛇游戏设计(八)——状态机设计

基于fpga的贪吃蛇

1. 状态机理论知识

Verilog语言可以依靠不同的always语句块实现硬件电路的并行执行,但在实际工程中,不仅需要并行执行电路,偶尔也会遇到需要串行执行的电路。这时候可以选择有限状态机FSM(Finite State Machine)来实现。

状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。

有限状态机主要分为2大类:

Mealy状态机:时序逻辑的输出不仅取决于当前状态,还与输入有关;

Moore状态机:时序逻辑的输出只与当前状态有关。

贪吃蛇游戏采用的是Mealy状态机模型。

根据代码的设计方式状态机可以分为一段式,二段式和三段式。

一段式写法就是把所有的逻辑(输入、输出和状态)都放在一个always语句块中,这种后期不容易维护。在简单的状态机可以使用。

二段式写法就是有两个always 块,把时序逻辑和组合逻辑分隔开来。时序逻辑里进行当前状态和下一状态的切换,组合逻辑实现各个输入、输出以及状态判断。这种写法不仅便于阅读、理解、维护,而且利于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现设计。在两段式描述中,当前状态的输出用组合逻辑实现,可能存在竞争和冒险,产生毛刺。

要求对状态机的输出用寄存器打一拍,但很多情况不允许插入寄存器节拍,此时使用三段式描述。其优势在于能够根据状态转移规律,在上一状态根据输入条件判断出当前状态的输出,从而不需要额外插入时钟节拍。

三段式写法就是有三个always 块,一个时序逻辑采用同步时序的方式描述状态转移,一个采用组合逻辑的方式判断状态转移条件、描述状态转移规律,第三个模块使用同步时序的方式描述每个状态的输出。三段式代码容易维护,时序逻辑的输出解决了两段式组合逻辑的毛刺问题,但是从资源消耗的角度上看,三段式的资源消耗多一些。

2. 游戏状态控制之状态机

这里用一段式状态机来完成游戏的控制过程:

关于这四个状态的描述请参考第一篇(整体描述):友晶科技FPGA:基于FPGA的贪吃蛇游戏设计(一)

3. 游戏状态控制完整源码

  1. /游戏控制模块 根据游戏状态产生相应控制信号
  2. module game_ctrl_unit
  3. (
  4. input clk,//25MHz
  5. input rst_n,//系统复位
  6. input key0_right,//方向控制按钮,控制向左移动
  7. input key1_left,//方向控制按钮,控制向右移动
  8. input key2_down,//方向控制按钮,控制向上移动
  9. input key3_up,//方向控制按钮,控制向下移动
  10. input [2:0]sw,//完成难度选择的操作
  11. input hit_wall,//撞墙标志
  12. input hit_body,//撞自身标志
  13. input [11:0]bcd_data,//分数累计到100则游戏成功
  14. output reg snake_display,//蛇整体显示标志
  15. output reg [1:0]game_status//当前游戏状态
  16. )
  17. ;
  18. //游戏分四个状态
  19. localparam RESTART = 2'b00; //游戏重启
  20. localparam START = 2'b01; //游戏开始
  21. localparam PLAY = 2'b10; //游戏进行
  22. localparam DIE = 2'b11; //游戏结束
  23. reg [32:0]cnt_clk;
  24. reg[31:0]flash_cnt;//蛇闪烁时间计数器
  25. //状态机定义初始状态,并描述状态转移与输出
  26. always@(posedge clk or negedge rst_n) begin
  27. if(!rst_n) begin
  28. cnt_clk<
  29. =
  30. 0
  31. ;
  32. flash_cnt<=0;
  33. snake_display<=1;
  34. game_status <= RESTART; //复位后游戏状态进入重启状态
  35. end
  36. else begin
  37. case(game_status)
  38. RESTART:begin //游戏重启状态
  39. cnt_clk<
  40. =cnt_clk+
  41. 1
  42. ;
  43. if(cnt_clk>150000000)begin// "欢迎来到贪吃蛇游戏“ 界面显示需要6s时间
  44. if(sw[0]||sw[1]||sw[2]) begin
  45. game_status <= START;//选择游戏难度后进入START状态
  46. end
  47. end
  48. else begin
  49. game_status <= RESTART;
  50. end
  51. end
  52. START:begin
  53. if ((~key0_right) ||( ~key1_left) || (~key2_down) ||( ~key3_up))//四个按键有任意一个按键被按下即可开始游戏
  54. game_status <
  55. = PLAY;
  56. else
  57. game_status <= START;
  58. end
  59. PLAY:begin
  60. if(hit_wall || hit_body||bcd_data[11:8]>=1'd1)//如果撞墙或者撞自身则 游戏结束
  61. game_status <
  62. = DIE;
  63. else
  64. game_status <= PLAY;
  65. end
  66. //下面代码是在制造闪烁效果
  67. //snake_display信号初始化的时候为高
  68. //snake_display信号在0-0.5秒为高,在 0.5-1秒为低,在 1-1.5秒高 在1.5-2低 2-2.5秒高 在2.5-3秒低
  69. DIE:begin
  70. if(flash_cnt <= 100_000_000) begin//flash_cnt计时4秒
  71. flash_cnt <
  72. = flash_cnt +
  73. 1'b
  74. 1;
  75. if(flash_cnt == 12_500_000)begin//0-0.5秒 高
  76. snake_display <= 1'b0;end
  77. else if(flash_cnt == 25_000_000)begin//0.5-1秒低
  78. snake_display <
  79. =
  80. 1'b
  81. 1;
  82. end
  83. else if(flash_cnt == 37_500_000)begin//1-1.5秒高
  84. snake_display <
  85. =
  86. 1'b
  87. 0;
  88. end
  89. else if(flash_cnt == 50_000_000)begin//1.5-2秒低
  90. snake_display <
  91. =
  92. 1'b
  93. 1;
  94. end
  95. else if(flash_cnt == 62_500_000)begin//2-2.5秒高
  96. snake_display <
  97. =
  98. 1'b
  99. 0;
  100. end
  101. else if(flash_cnt == 75_000_000)begin//2.5-3秒低
  102. snake_display <
  103. =
  104. 1'b
  105. 1;
  106. end
  107. end
  108. //游戏结束后按任意按键重新开始
  109. else if((~key0_right) ||( ~key1_left) || (~key2_down) ||( ~key3_up) ) begin
  110. cnt_clk<=0;
  111. flash_cnt<=0;
  112. game_status <= RESTART;
  113. end
  114. else begin
  115. game_status <= DIE;
  116. end
  117. end
  118. default:begin
  119. game_status <= RESTART; //游状态 从游戏结束 到游戏重启
  120. end
  121. endcase
  122. end
  123. end
  124. endmodule

4. 2个设计点提醒

这个cnt_clk计数器这里不能写成cnt_clk=150000000,因为第6s的时间只有一瞬间,当这个时候没有拨动SW时,其他时刻它就不再去判断SW的情况了。

当游戏结束时,按任意键触发游戏重新开始,cnt_clk和flash_cnt两个计数器清零,不然重新开始的时候会跳过欢迎界面,以及再次游戏结束时蛇不会闪烁。

其实就是要确保在重启状态下,所有的条件要回到跟复位时一样。

5. 蛇身方向控制状态机

蛇身方向控制用一个三段式的4状态的状态机来实现。四个状态分别是上、下、左、右。

状态机第一段

完成初始化状态,以及完成当前状态转换到下一状态的时序逻辑:

首先方向一定要初始化,否则方向就会错乱, 虽然这里初始化方向是向右,但实际上运行时起步方向的判断是根据key值来判断。

状态机第二段

在当前状态下,根据当前的输入转换为下一状态的组合逻辑。

根据按键进行三个方向的选择,这里是按键按下的时候,信号传导Direct_next,然后由Direct_next送给Direct_r:

状态机第三段

根据当前状态和当前输入产生当前输出的时序逻辑:

上面这段代码解释如下:

  • 当方向向上且不撞到墙壁时,蛇头的y轴坐标+1, 也就是蛇向上移动一格。

  • 当方向向下且不撞到墙壁时,蛇头的y轴坐标-1, 也就是蛇向下移动一格。

  • 当方向向左且不撞到墙壁时,蛇头的x轴坐标-1, 也就是蛇向左移动一格。

  • 当方向向上且不撞到墙壁时,蛇头的x轴坐标+1, 也就是蛇向右移动一格。

贪吃蛇系列连载文章:

 ​
1. 基于FPGA的贪吃蛇游戏设计(一)

2. 基于FPGA的贪吃蛇游戏设计(二)——数码管驱动模块

3. 基于FPGA的贪吃蛇游戏设计(三)——计分模块

4. 基于FPGA的贪吃蛇游戏设计(四)——VGA驱动模块色块显示

5. 基于FPGA的贪吃蛇游戏设计(五)——VGA驱动模块字符显示

6. 基于FPGA的贪吃蛇游戏设计(六)——VGA驱动模块图片显示

7. 基于FPGA的贪吃蛇游戏设计(七)——食物(苹果)的产生

8. 基于FPGA的贪吃蛇游戏设计(八)——状态机设计

9. 基于FPGA的贪吃蛇游戏设计(九)——蛇身控制

关注“友晶Terasic”公众号可获取源码下载地址。

移植到DE1-SOC、DE2-115时代码无需改变,只需修改引脚分配即可。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/775843
推荐阅读
相关标签
  

闽ICP备14008679号