当前位置:   article > 正文

基于Verilog的贪吃蛇小游戏设计(附代码)_verilog hdl的贪吃蛇游戏

verilog hdl的贪吃蛇游戏

    本文介绍基于verilog语言开发的贪吃蛇小游戏,FPGA板卡至少需要900个LC(文中程序需求1700个LC,可通过删减部分代码得到)、4个独立按键以及1个VGA接口。本文使用的板卡配有50MHz晶振,所配备的程序生成800*600*60Hz驱动信号,采用其它晶振频率的板卡需要对VGA驱动渲染模块(本文中为render)进行相应修改,使得刷新脉冲符合显示屏输入信号要求。

    注意:本文中使用snack代指snake,这是由于最初的错误累积而成,不得已对其进行忽略。

    贪吃蛇工程由7个模块构成,结构层级如图1所示。其中包含1个顶层模块snack、3个功能模块(random_box、render、snack_control)以及3个底层模块(keycheck、random、box_create)。本文设计的结构层次存在一定的瑕疵,其修正方法会在各个模块中提出,但由于本人水平有限,也有部分问题未能解决,欢迎各位能够与我进行探讨。

   

                                                             图1 贪吃蛇工程结构层次

1. 顶层模块snack

    顶层模块主要规定了工程对外的各类接口,以及各个功能模块之间的关系。在本文中,顶层模块调用了三个功能模块(random_box、render、snack_control),作为游戏功能的分区设计。将整个工程所需要实现的功能肢解为随机小盒子(即苹果或被吃物的创建与更新)、VGA驱动脉冲生成与随机小盒子画面渲染、蛇的控制(移动方向、体长、行为检测)。其RTL视图如图2所示。

                                                    图2 顶层模块RTL视图

    在此处,出现了本文的第一个设计瑕疵:功能划分不清。在设计中,将随机小盒子的画面渲染放到render模块中,却将蛇的渲染放置到snack_control模块中,造成了模块的功能混淆。更加理性地办法是将随机小盒子的渲染放到random_box模块中,render模块只负责VGA驱动信号的生成以及画面总体渲染。这样工程模块的划分也更加清晰,任务也更加明确。

顶层设计的代码如下:

  1. module snack(
  2. input clk,
  3. input rst_n,
  4. input key_r,
  5. input key_l,
  6. input key_u,
  7. input key_d,
  8. output hsync,
  9. output vsync,
  10. output vga_r,
  11. output vga_g,
  12. output vga_b
  13. );
  14. //贪吃蛇游戏分为:box生成模块、蛇身控制模块、画面渲染模块
  15. //蛇:红色
  16. //box:绿色
  17. //背景:蓝色
  18. //--------------------------------------------------------------------------------
  19. //画面渲染位置
  20. wire [9:0]x_pos;
  21. wire [9:0]y_pos;
  22. //box坐标传递
  23. wire[9:0]box_x;
  24. wire[9:0]box_y;
  25. //--------------------------------------------------------------------------------
  26. //蛇身控制模块,蛇是红色的
  27. wire drive;
  28. wire snack_r;
  29. snack_control u1_snack_control(
  30. .clk(clk),
  31. .rst_n(rst_n),
  32. .key_r(key_r),
  33. .key_l(key_l),
  34. .key_u(key_u),
  35. .key_d(key_d),
  36. .box_x(box_x),
  37. .box_y(box_y),
  38. .x_pos(x_pos),
  39. .y_pos(y_pos),
  40. .drive(drive),
  41. .snack_r(snack_r)
  42. );
  43. //--------------------------------------------------------------------------------
  44. //box生成模块
  45. random_box u1_random_box(
  46. .clk(clk),
  47. .rst_n(rst_n),
  48. .drive(drive),//生成驱动信号
  49. .box_x(box_x),//坐标信号
  50. .box_y(box_y)
  51. );
  52. //--------------------------------------------------------------------------------
  53. //画面渲染模块
  54. render u1_render(
  55. .clk(clk),
  56. .rst_n(rst_n),
  57. .box_x(box_x),
  58. .box_y(box_y),
  59. .snack_r(snack_r),
  60. .hsync(hsync),
  61. .vsync(vsync),
  62. .x_pos(x_pos),
  63. .y_pos(y_pos),
  64. .vga_r(vga_r),
  65. .vga_g(vga_g),
  66. .vga_b(vga_b)
  67. );
  68. endmodule

 

2. random_box模块

    该模块主要负责随机小盒子的生成与更新。盒子的坐标来自于LFSR伪随机码生成器产生的两个相邻数据,为了获取完整的随机坐标,必须等待2个时钟周期。

该模块的代码如下:

  1. module random_box(
  2. input clk,
  3. input rst_n,
  4. input drive,
  5. output wire[9:0]box_x,
  6. output wire[9:0]box_y
  7. );
  8. //---------------------------------------------------------------------------------
  9. //随机数生成模块
  10. wire [8:0]rand_num;
  11. random U1_random(
  12. .clk(clk),
  13. .rst_n(rst_n),
  14. // .seed(seed),
  15. //.load(load),
  16. .rand_num(rand_num)
  17. );
  18. //随机盒子创建
  19. wire [9:0]rand_x;
  20. wire [9:0]rand_y;
  21. // wire rand_drive;//随机小方块激励模块
  22. box_create U1_box_create(
  23. .clk(clk),
  24. .rst_n(rst_n),
  25. .rand_num(rand_num),
  26. .rand_drive(drive),
  27. .rand_x(rand_x),
  28. .rand_y(rand_y)
  29. );
  30. assign box_x = rand_x;
  31. assign box_y = rand_y;
  32. endmodule

2.1 random模块

    该模块采用LFSR机制生成伪随机数,其种子已提前预置。本文中采用了9位宽度的设计,使得生成的伪随机数范围小于VGA屏幕的显示范围。如果有需求可为LFSR增加1位长度,并增添额外的代码用于判定取用的伪随机数是否越界。

其代码如下所示:

  1. module random(
  2. input clk,
  3. input rst_n,
  4. // input load,
  5. // input[8:0] seed,
  6. output reg[8:0] rand_num
  7. );
  8. //-----------------------------------------------------------------------------------
  9. always@(posedge clk or negedge rst_n)
  10. if(!rst_n) rand_num <= 9'd132;
  11. // else if(load) rand_num <= seed;
  12. // else if(load) rand_num <= 9'd131;
  13. else
  14. begin
  15. rand_num[0] <= rand_num[8];
  16. rand_num[1] <= rand_num[0];
  17. rand_num[2] <= rand_num[1];
  18. rand_num[3] <= rand_num[2];
  19. rand_num[4] <= rand_num[3]^rand_num[8];
  20. rand_num[5] <= rand_num[4]^rand_num[8];
  21. rand_num[6] <= rand_num[5]^rand_num[8];
  22. rand_num[7] <= rand_num[6];
  23. rand_num[8] <= rand_num[7];
  24. end
  25. endmodule

2.2 box_create模块

    该模块主要用于创建和更新随机小盒子的中心坐标。更新信号drive由snack_control模块提供,为一个仅保持1个时钟周期拉高信号。在模块中配置flag信号是为了为信号坐标的获取实行一个周期的延时,避免了横纵坐标的相等。

其代码如下:

  1. module box_create(
  2. input clk,
  3. input rst_n,
  4. input[8:0] rand_num,
  5. input rand_drive,
  6. output reg[9:0]rand_x,
  7. output reg[9:0]rand_y
  8. );
  9. //-----------------------------------------------------------------------------
  10. reg flag;
  11. always@(posedge clk or negedge rst_n)
  12. if(!rst_n)
  13. begin
  14. rand_x <= 9'd300;
  15. rand_y <= 9'd300;
  16. flag <= 1'b0;
  17. end
  18. else if(rand_drive) begin flag <= 1'b1; rand_x <= rand_num; end
  19. else if(flag == 1'b1) begin rand_y <= rand_num; flag <= 1'b0;end
  20. endmodule

3. snack_control模块

    该模块主要实现了以下几个功能:蛇头移动方向控制、蛇体寄存器、死亡检测、长度检测、蛇体渲染等四部分构成。蛇头移动方向控制由按键检测模块和方向寄存器修正规则代码组成;蛇体寄存器由蛇头移动更新状态机、蛇身移位寄存器组组成;长度检测包含长度记录、增长控制、随机小盒子更新信号生成三个功能;蛇体渲染为根据VGA驱动信号计算蛇体在屏幕上的显示,如果采用未注释部分,综合出的代码需要900个LC,若采用注释部分的代码增粗蛇体,就需要1700个LC,这也是本设计中的另一个不足。

其代码如下:

  1. module snack_control(
  2. input clk,
  3. input rst_n,
  4. input key_r,
  5. input key_l,
  6. input key_u,
  7. input key_d,
  8. input [9:0]box_x,
  9. input [9:0]box_y,
  10. input [9:0]x_pos,
  11. input [9:0]y_pos,
  12. output reg drive,
  13. output snack_r
  14. );
  15. //------------------------------------------------------------------------------
  16. //蛇头移动方向控制
  17. wire key_r1;
  18. wire key_l1;
  19. wire key_u1;
  20. wire key_d1;
  21. reg[1:0]dir;//蛇头方向寄存器
  22. parameter right = 2'd0;
  23. parameter left = 2'd1;
  24. parameter up = 2'd2;
  25. parameter down = 2'd3;
  26. keycheck r_keycheck(
  27. .clk(clk),
  28. .rst_n(rst_n),
  29. .key(key_r),
  30. .key_v(key_r1)
  31. );
  32. keycheck l_keycheck(
  33. .clk(clk),
  34. .rst_n(rst_n),
  35. .key(key_l),
  36. .key_v(key_l1)
  37. );
  38. keycheck u_keycheck(
  39. .clk(clk),
  40. .rst_n(rst_n),
  41. .key(key_u),
  42. .key_v(key_u1)
  43. );
  44. keycheck d_keycheck(
  45. .clk(clk),
  46. .rst_n(rst_n),
  47. .key(key_d),
  48. .key_v(key_d1)
  49. );
  50. always@(posedge clk or negedge rst_n)
  51. if(!rst_n) dir <= right;
  52. else if(key_r1 && dir != left) dir <= right;
  53. else if(key_l1 && dir != right) dir <= left;
  54. else if(key_u1 && dir != down) dir <= up;
  55. else if(key_d1 && dir != up) dir <= down;
  56. //------------------------------------------------------------------------------
  57. //蛇体寄存器,蛇体坐标和当前移动方向
  58. reg fin;//游戏结束标志位
  59. reg[9:0]snack_x[11:0];
  60. reg[9:0]snack_y[11:0];
  61. reg [18:0]cnt_m; //最大值312500;
  62. parameter update_time = 19'd312500;
  63. //更新计数器
  64. always@(posedge clk or negedge rst_n)
  65. if(!rst_n) cnt_m <= 19'd0;
  66. else if(fin) cnt_m <= 19'd0;
  67. else if(cnt_m == update_time) cnt_m <= 19'd0;
  68. else cnt_m <= cnt_m + 19'd1;
  69. //蛇头
  70. always@(posedge clk or negedge rst_n)
  71. if(!rst_n)
  72. begin
  73. snack_x[0] <= 10'd400;
  74. snack_y[0] <= 10'd300;
  75. end
  76. else if(cnt_m == update_time)
  77. case(dir)
  78. right : begin
  79. if(snack_x[0] == 10'd799) snack_x[0] <= 10'd0;
  80. else snack_x[0] <= snack_x[0] + 10'd1;
  81. end
  82. left : begin
  83. if(snack_x[0] == 10'd0) snack_x[0] <= 10'd799;
  84. else snack_x[0] <= snack_x[0] - 10'd1;
  85. end
  86. up : begin
  87. if(snack_y[0] == 10'd599) snack_y[0] <= 10'd0;
  88. else snack_y[0] <= snack_y[0] + 10'd1;
  89. end
  90. down : begin
  91. if(snack_y[0] == 10'd0) snack_y[0] <= 10'd599;
  92. else snack_y[0] <= snack_y[0] - 10'd1;
  93. end
  94. endcase
  95. //蛇身
  96. always@(posedge clk or negedge rst_n)
  97. if(!rst_n)
  98. begin
  99. snack_x[1] <= 10'd0;
  100. snack_y[1] <= 10'd0;
  101. snack_x[2] <= 10'd0;
  102. snack_y[2] <= 10'd0;
  103. snack_x[3] <= 10'd0;
  104. snack_y[3] <= 10'd0;
  105. snack_x[4] <= 10'd0;
  106. snack_y[4] <= 10'd0;
  107. snack_x[5] <= 10'd0;
  108. snack_y[5] <= 10'd0;
  109. snack_x[6] <= 10'd0;
  110. snack_y[6] <= 10'd0;
  111. snack_x[7] <= 10'd0;
  112. snack_y[7] <= 10'd0;
  113. snack_x[8] <= 10'd0;
  114. snack_y[8] <= 10'd0;
  115. snack_x[9] <= 10'd0;
  116. snack_y[9] <= 10'd0;
  117. snack_x[10] <= 10'd0;
  118. snack_y[10] <= 10'd0;
  119. snack_x[11] <= 10'd0;
  120. snack_y[11] <= 10'd0;
  121. end
  122. else if(cnt_m == update_time)
  123. begin
  124. snack_x[1] <= snack_x[0];
  125. snack_y[1] <= snack_y[0];
  126. snack_x[2] <= snack_x[1];
  127. snack_y[2] <= snack_y[1];
  128. snack_x[3] <= snack_x[2];
  129. snack_y[3] <= snack_y[2];
  130. snack_x[4] <= snack_x[3];
  131. snack_y[4] <= snack_y[3];
  132. snack_x[5] <= snack_x[4];
  133. snack_y[5] <= snack_y[4];
  134. snack_x[6] <= snack_x[5];
  135. snack_y[6] <= snack_y[5];
  136. snack_x[7] <= snack_x[6];
  137. snack_y[7] <= snack_y[6];
  138. snack_x[8] <= snack_x[7];
  139. snack_y[8] <= snack_y[7];
  140. snack_x[9] <= snack_x[8];
  141. snack_y[9] <= snack_y[8];
  142. snack_x[10] <= snack_x[9];
  143. snack_y[10] <= snack_y[9];
  144. snack_x[11] <= snack_x[10];
  145. snack_y[11] <= snack_y[10];
  146. end
  147. //------------------------------------------------------------------------------
  148. //长度检测模块
  149. reg[3:0] length;
  150. always@(posedge clk or negedge rst_n)
  151. if(!rst_n) begin length <= 4'd1;drive <= 1'd0; end
  152. else if(drive) drive <= 1'd0;
  153. else if(snack_x[0] == box_x && snack_y[0] == box_y)
  154. begin
  155. drive <= 1'd1;
  156. if(length < 4'd12) length <= length + 4'd1;
  157. else length <= length;
  158. end
  159. //-------------------------------------------------------------------------------
  160. //死亡检测模块
  161. //reg fin;
  162. always@(posedge clk or negedge rst_n)
  163. if(!rst_n) fin <= 1'b0;
  164. else if(snack_x[0] == snack_x[1] && snack_y[0] == snack_y[1]) fin <= 1'b1;
  165. else if(snack_x[0] == snack_x[2] && snack_y[0] == snack_y[2]) fin <= 1'b1;
  166. else if(snack_x[0] == snack_x[3] && snack_y[0] == snack_y[3]) fin <= 1'b1;
  167. else if(snack_x[0] == snack_x[4] && snack_y[0] == snack_y[4]) fin <= 1'b1;
  168. else if(snack_x[0] == snack_x[5] && snack_y[0] == snack_y[5]) fin <= 1'b1;
  169. else if(snack_x[0] == snack_x[6] && snack_y[0] == snack_y[6]) fin <= 1'b1;
  170. else if(snack_x[0] == snack_x[7] && snack_y[0] == snack_y[7]) fin <= 1'b1;
  171. else if(snack_x[0] == snack_x[8] && snack_y[0] == snack_y[8]) fin <= 1'b1;
  172. else if(snack_x[0] == snack_x[9] && snack_y[0] == snack_y[9]) fin <= 1'b1;
  173. else if(snack_x[0] == snack_x[10] && snack_y[0] == snack_y[10]) fin <= 1'b1;
  174. else if(snack_x[0] == snack_x[11] && snack_y[0] == snack_y[11]) fin <= 1'b1;
  175. //-------------------------------------------------------------------------------
  176. //蛇体渲染模块
  177. assign snack_r = (x_pos == snack_x[0] && y_pos == snack_y[0])||
  178. (x_pos == snack_x[1] && y_pos == snack_y[1]&&length > 4'd1)||
  179. (x_pos == snack_x[2] && y_pos == snack_y[2]&&length > 4'd2)||
  180. (x_pos == snack_x[3] && y_pos == snack_y[3]&&length > 4'd3)||
  181. (x_pos == snack_x[4] && y_pos == snack_y[4]&&length > 4'd4)||
  182. (x_pos == snack_x[5] && y_pos == snack_y[5]&&length > 4'd5)||
  183. (x_pos == snack_x[6] && y_pos == snack_y[6]&&length > 4'd6)||
  184. (x_pos == snack_x[7] && y_pos == snack_y[7]&&length > 4'd7)||
  185. (x_pos == snack_x[8] && y_pos == snack_y[8]&&length > 4'd8)||
  186. (x_pos == snack_x[9] && y_pos == snack_y[9]&&length > 4'd9)||
  187. (x_pos == snack_x[10] && y_pos == snack_y[10]&&length > 4'd10)||
  188. (x_pos == snack_x[11] && y_pos == snack_y[11]&&length > 4'd11);
  189. /*
  190. assign snack_r = ((x_pos >= snack_x[0]-10'd3 && x_pos <= snack_x[0]+10'd3)&&(y_pos >= snack_y[0]-10'd3 && y_pos <= snack_y[0]+10'd3))||
  191. ((x_pos >= snack_x[1]-10'd3 && x_pos <= snack_x[1]+10'd3)&&(y_pos >= snack_y[1]-10'd3 && y_pos <= snack_y[1]+10'd3)&&length > 4'd1)||
  192. ((x_pos >= snack_x[2]-10'd3 && x_pos <= snack_x[2]+10'd3)&&(y_pos >= snack_y[2]-10'd3 && y_pos <= snack_y[2]+10'd3)&&length > 4'd2)||
  193. ((x_pos >= snack_x[3]-10'd3 && x_pos <= snack_x[3]+10'd3)&&(y_pos >= snack_y[3]-10'd3 && y_pos <= snack_y[3]+10'd3)&&length > 4'd3)||
  194. ((x_pos >= snack_x[4]-10'd3 && x_pos <= snack_x[4]+10'd3)&&(y_pos >= snack_y[4]-10'd3 && y_pos <= snack_y[4]+10'd3)&&length > 4'd4)||
  195. ((x_pos >= snack_x[5]-10'd3 && x_pos <= snack_x[5]+10'd3)&&(y_pos >= snack_y[5]-10'd3 && y_pos <= snack_y[5]+10'd3)&&length > 4'd5)||
  196. ((x_pos >= snack_x[6]-10'd3 && x_pos <= snack_x[6]+10'd3)&&(y_pos >= snack_y[6]-10'd3 && y_pos <= snack_y[6]+10'd3)&&length > 4'd6)||
  197. ((x_pos >= snack_x[7]-10'd3 && x_pos <= snack_x[7]+10'd3)&&(y_pos >= snack_y[7]-10'd3 && y_pos <= snack_y[7]+10'd3)&&length > 4'd7)||
  198. ((x_pos >= snack_x[8]-10'd3 && x_pos <= snack_x[8]+10'd3)&&(y_pos >= snack_y[8]-10'd3 && y_pos <= snack_y[8]+10'd3)&&length > 4'd8)||
  199. ((x_pos >= snack_x[9]-10'd3 && x_pos <= snack_x[9]+10'd3)&&(y_pos >= snack_y[9]-10'd3 && y_pos <= snack_y[9]+10'd3)&&length > 4'd9)||
  200. ((x_pos >= snack_x[10]-10'd3 && x_pos <= snack_x[10]+10'd3)&&(y_pos >= snack_y[10]-10'd3 && y_pos <= snack_y[10]+10'd3)&&length > 4'd10)||
  201. ((x_pos >= snack_x[11]-10'd3 && x_pos <= snack_x[11]+10'd3)&&(y_pos >= snack_y[11]-10'd3 && y_pos <= snack_y[11]+10'd3)&&length > 4'd11);
  202. */
  203. endmodule

   

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

闽ICP备14008679号