找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3066|回复: 2
收起左侧

俄罗斯方块Verilog HDL程序设计

[复制链接]
ID:704585 发表于 2021-3-19 17:37 | 显示全部楼层 |阅读模式
通过此次项目,完成以下目的:
  • 熟悉Xilinx FPGA的架构及开发流程
  • 设计一个功能完整的系统掌握FSM + Datapath的设计方法。
实验内容
  • 项目介绍
本项目主要在FPGA上实现了一个经典小游戏“俄罗斯方块”。本项目基本解决方案是,使用Xilinx Zynq系列开发板ZedBoard作为平台,实现主控模块,通过VGA接口来控制屏幕进行显示。
  • 系统框架
整个系统由四部分组成,按键输入处理模块、控制模块、数据路径模块以及VGA显示接口模块。整个系统的结构如下图所示:
图1:系统框图
下面分别对四个模块进行介绍:
  • 按键输入处理模块
按键处理模块的主要功能是对输入系统的up,down,left,right四个控制信号进行消抖处理,并对其进行上升沿检测。
消抖模块采用上课所提出的结构,采用了一个4位的移位寄存器,先将输入信号延迟4个时钟周期,再对其以一个较低的时钟频率进行采用。消抖模块的结构如下图所示:
图2:消抖模块结构示意图
为了简化控制系统,在本系统的设计过程中,不考虑长时间按键产生连按效果。因而,需要对按键进行上升沿检测。上升沿检测的基本实现方案是加入一组寄存器,对前一个的按键信号进行暂存,将暂存的值与当前值进行比较,当上一个值为0而当前值为1时,即认为其检测到了一个上升沿。
  • 控制模块
控制模块采用FSM的方式进行控制。在控制模块中,定义了10个状态:
S_idle:上电复位后进入的空状态,当start信号为1时进入S_new状态
S_new:用于产生新的俄罗斯方块。
S_hold:保持状态。在这个状态中进行计时,当时间到达一定间隔时,转到S_down状态;或者等待输入信号(up,down,left,right)时,转到S_down(按键为down)或者S_move(up,left,right)状态。
S_down:判断当前俄罗斯块能否下移一格。如果可以,则转到S_remove_1状态,如果不行,则转到S_shift状态。
S_move:判断当前俄罗斯块能够按照按键信号指定的指令进行移动,如果可以,则转到S_shift状态,如果不可以,则转到S_remove_1状态。
S_shift:更新俄罗斯方块的坐标信息。返回S_hold。
S_remove_1:更新整个屏幕的矩阵信息。转移到S_remove_2状态。
S_remove_2:判断是否可以消除,将可以消除的行消除,并将上面的行下移一行。重复此过程,直到没有可消除的行为止。跳转到S_isdie状态
S_isdie:判断是否游戏结束。如果结束,则跳转到S_stop状态。如果没有,则跳转到S_new状态,生成新的俄罗斯方块。
S_stop:清楚整个屏幕,并跳转到S_idle状态。
整个控制过程的ASMD图如下图所示:
图3: 控制模块ASMD图
  • 数据路径
数据路径模块主要功能是,根据控制模块给出的信号,对俄罗斯方块当前的逻辑状态进行判断,更新背景矩阵。具体如下:
方块:
方块分为非活动方块与活动方块。非活动方块为:(1)之前下落的方块;(2)下落后方块消除之后的结果。由背景矩阵表示。活动方块为当前下落中的方块,由活动方块坐标与方块类型表示(后简称方块)。
背景矩阵:
reg [9:0] R [23:0];
背景矩阵R是24行10列的寄存器组,负责保存非活动方块坐标,即R中任一位置,如方块存在,则该位置1,否则为0。
活动方块坐标:
output reg [4:0] n,
output reg [3:0] m,
n, m分别为当前活动方块的行、列指针,指向方块固定点位置。方块固定点为方块旋转时不变的格点,依据方块种类决定,下文方块模型中详述。
方块类型:
output reg [6:0] BLOCK,
BLOCK代表方块类型,由7位编码构成。
数据交换:
Datapath与其余模块的数据交换分为两部分:(1)与control_unit间的状态指令交互;(2)控制merge,间接实现对VGA的控制。
方块模型:
俄罗斯方块共有7中形状的方块(O,L,J,I,T,Z,S),每种方块有1-4种不同的旋转变形方式。为方便起见,将方块定位A-G,旋转编号为1-4,将方块编码成A_1-G_2的19种,如  下图:
图中,深色方块是该种方块的固定点。
图4: 方块模型示意图
              方块运动:
产生:
方块产生由一个简单的伪随机过程决定。系统采用一个3位的计数器产生随机数,进入S_new,BLOCK的值被NEW_BLOCK覆盖,方块坐标n<=1;m<=5;同时,根据计数器,NEW_BLOCK的值刷新为A_1,B_1,…,G_1中的一种,作为下一次方块。
移动:
方块移动分为四种:旋转,下落,向左,向右,由键盘KEYBOARD=[UP, DOWN, LEFT, RIGHT]控制。移动分两步进行:(1)判断;(2)转换。
判断过程包含S_down,S_move。判断分两步:首先,判断变换后方块坐标是否合法,即变换后是否会造成方块越界。然后,判断变换后方块可能占据的新位置是否有背景矩阵方块存在。两步判断通过后返回成功信号,否则失败。因判断代码量较多,仅举一例说明:
判断D_1向右运动(MOVE_ABLE初值为0):
if (m<=8)
if (!((R[n-1][m+1])|(R[n][m+1])|(R[n+1][m+1])|(R[n+2][m+1])))
MOVE_ABLE=1;
else MOVE_ABLE=0;
              转换过程(S_shift)进行方块的移动或变形。根据KEYBOARD,移动时,改变方块坐标;变形时,方块按类别变换,如:A_1→A_1;B_1→B_2; B_2→B_3; B_4→B_1;
停止与消除:
方块停止与消除由两个状态完成:S_remove1,S_remove2。
前一状态中,根据BLOCK, n, m,将活动方块位置覆盖至R,变为非活动方块。
后一状态中,根据行满状态,进行行的消除与平移,具体如下:
显然,俄罗斯方块能影响的最大行数为4,因此,在REMOVE_2中,仅对R[n-1],R[n],R[n+1],R[n+2]四行依次进行处理。处理过程为:如果该行(k)满,则由k行开始,至1行结束,逐行向下平移,当前平移位置由计数器REMOVE_2_C控制,当前行消除截止由标志位SIG确认。
每行处理完后,将REMOVE_FINISH[3:0]中相应位置1,REMOVE_FINISH全1时,REMOVE_2完成。
死亡判定:
R中的0-3行位于屏幕上方,不进行显示,仅有新生成的方块坐标会进入这一区域。因而,当消除完成后,如R[3]不为空,游戏结束。
  • 显示部分
输出结果通过VGA接口接入显示屏显示。VGA(Video Graphics Array)视频图形阵列是IBM于1987年提出的一个使用模拟信号的电脑显示标准。VGA接口即电脑采用VGA标准输出数据的专用接口。VGA接口共有15针,分成3排,每排5个孔,显卡上应用最为广泛的接口类型,绝大多数显卡都带有此种接口。它传输红、绿、蓝模拟信号以及同步信号(水平和垂直信号)。
使用Verilog HDL语言对VGA进行控制一般只需控制行扫描信号、列扫描信号和红绿蓝三色信号输出即可。
VGA输出可分为四个模块:时钟分频模块、数据组织模块、接口控制模块和顶层模块。以下进行分块描述。
时钟模块分频模块对FPGA系统时钟进行分频。由于使用的显示屏参数为640*480*60Hz,其真实屏幕大小为800*525,因此所需时钟频率为800*525*60Hz=25.175MHz,可近似处理为25MHz。FPGA系统时钟为100M,因此将其四分频即可基本满足显示要求。
数据组织模块是将预备输出的数据组织为可以通过VGA接口控制的数据形式,本次设计中因接口已经协调,数据可不经过此模块进行组织,故可忽略该模块。
接口控制模块通过VGA接口对显示屏进行控制。VGA的扫描顺序是从左到右,从上到下。例如在640X480的显示模式下,从显示器的左上角开始往右扫描,直到640个像素扫完,再回到最左边,开始第二行的扫描,如此往复,到第480行扫完时即完成一帧图像的显示。这时又回到左上角,开始下一帧图像的扫描。如果每秒能完成60帧,则称屏幕刷新频率为60Hz。宏观上,一帧屏幕由480个行和640个列填充而成,而实际上,一帧屏幕除了显示区,还包含其他未显示部分,作为边框或者用来同步。具体而言,一个完整的行同步信号包含了左边框、显示区、右边框还有返回区四个部分,总共800个像素,其分配如下:
图5: VGA行扫描时序
同样的,一个完整的垂直同步信号也分为四个区域,总共525个像素,分配如下:
图6:VGA场扫描时序
模块通过组织输出行扫描信号、列扫描信号和三原色信号对显示屏实现控制。
实验结果
实验结果图如下:
图7:实验结果图
实验总结



    • 完成情况
本次项目我们完成了既定目标,即完成一个经典小游戏“俄罗斯方块”的核心功能。在本次实验过程中,我们通过采用分工合作的方式,通过对系统功能的分析,确定解决方案,完成了对一个系统自上而下的设计,并尝试采用控制单元+数据路径这样的方式来处理核心模块。



    • 不足与改进之处
由于时间仓促,加之对俄罗斯方块逻辑复杂度估计不足,到最后展示之前我们才完成了对核心模块的调试。因此,在用户界面上没有做过多的调整。另外,由于在进行模块划分时,一些接口没有事先定义好,导致在最后系统整合时,不得不进行修改与调整,由此而造成了一部分时间的浪费。
总的来说,通过这个项目,小组成员对于硬件设计“并行”的特点有了比较直接的认识,同时也在调试的过程中掌握了一些硬件调试常用的方法,也认识到了仿真的重要意义所在。另外就是关于团队协作方面的一个教训,在系统划分时要注意把接口定义好,以免造成不必要的代价。
实验代码
KeyBoard模块
  1. `timescale 1ns / 1ps
  2. module key(
  3.     input clk,
  4.     input rst_n,
  5.     input UP_KEY,
  6.     input LEFT_KEY,
  7.     input RIGHT_KEY,
  8.     input DOWN_KEY,
  9.     output reg rotate,
  10.     output reg left,
  11.     output reg right,
  12.     output reg down
  13.     );

  14.     reg [3:0] shift_up;
  15.     reg [3:0] shift_left;
  16.     reg [3:0] shift_right;
  17.     reg [3:0] shift_down;

  18.     always @(posedge clk or negedge rst_n)
  19.     begin
  20.         if (!rst_n)
  21.             shift_up <= 0;
  22.         else
  23.             shift_up <= {shift_up[2:0], UP_KEY};
  24.     end

  25.     always @(posedge clk or negedge rst_n)
  26.     begin
  27.         if (!rst_n)
  28.             shift_right <= 0;
  29.         else
  30.             shift_right <= {shift_right[2:0], RIGHT_KEY};
  31.     end

  32.     always @(posedge clk or negedge rst_n)
  33.     begin
  34.         if (!rst_n)
  35.             shift_left <= 0;
  36.         else
  37.             shift_left <= {shift_left[2:0], LEFT_KEY};
  38.     end

  39.     always @(posedge clk or negedge rst_n)
  40.     begin
  41.         if (!rst_n)
  42.             shift_down <= 0;
  43.         else
  44.             shift_down <= {shift_down[2:0], DOWN_KEY};
  45.     end


  46. reg clk_div;
  47. reg [7:0] clk_cnt;
  48. always @ (posedge clk or negedge rst_n)
  49. begin
  50.     if (!rst_n)
  51.     begin
  52.         clk_cnt <= 0;
  53.         clk_div <= 0;
  54.     end
  55.     else if (clk_cnt <= 8'd49)
  56.     begin
  57.         clk_cnt <= clk_cnt + 1;
  58.         clk_div <= clk_div;
  59.     end
  60.     else
  61.     begin
  62.         clk_cnt <= 0;
  63.         clk_div <= ~clk_div;
  64.     end
  65. end

  66. always @(posedge clk_div or negedge rst_n)
  67. begin
  68.     if (!rst_n)
  69.     begin
  70.         rotate <= 0;
  71.         left <= 0;
  72.         right <= 0;
  73.         down <= 0;
  74.     end
  75.     else
  76.     begin
  77.         rotate <= shift_up[3];
  78.         left <= shift_left[3];
  79.         right <= shift_right[3];
  80.         down <= shift_down[3];
  81.     end
  82. end

  83. endmodule


  84. 控制模块程序
  85. module game_control_unit (
  86.     input clk,
  87.     input rst_n,
  88.     input rotate,
  89.     input left,
  90.     input right,
  91.     input down,
  92.     input start,
  93.     output reg [3:0] opcode,
  94.     output reg gen_random,
  95.     output reg hold,
  96.     output reg shift,
  97.     output reg move_down,
  98.     output reg remove_1,
  99.     output reg remove_2,
  100.     output reg stop,
  101.     output reg move,
  102.     output reg isdie,
  103.     output reg auto_down,
  104.     input shift_finish,
  105.     input remove_2_finish,
  106.     input down_comp,
  107.     input move_comp,
  108.     input die
  109.     );

  110.     reg left_reg;
  111.     reg right_reg;
  112.     reg up_reg;
  113.     reg down_reg;

  114.     always @(posedge clk or negedge rst_n)
  115.     begin
  116.         if (!rst_n)
  117.         begin
  118.             left_reg <= 0;
  119.             right_reg <= 0;
  120.             up_reg <= 0;
  121.             down_reg <= 0;
  122.         end
  123.         else
  124.         begin
  125.             left_reg <= left;
  126.             right_reg <= right;
  127.             up_reg <= rotate;
  128.             down_reg <=  down;
  129.         end
  130.     end

  131.     reg auto_down_reg;
  132.     always @ (posedge clk or negedge rst_n)
  133.     begin
  134.         if (!rst_n)
  135.             auto_down_reg <= 0;
  136.         else if (time_cnt == time_val)
  137.             auto_down_reg <= 1;
  138.         else
  139.             auto_down_reg <= 0;
  140.     end

  141.     always @ (posedge clk or negedge rst_n)
  142.     begin
  143.         if (!rst_n)
  144.             auto_down <= 0;
  145.         else
  146.             auto_down <= auto_down_reg;
  147.     end

  148.     parameter time_val = 26'd25000001;
  149.     reg [25:0] time_cnt;

  150.     localparam  S_idle      = 4'd0,
  151.                 S_new       = 4'd1,
  152.                 S_hold      = 4'd2,
  153.                 S_move      = 4'd3,
  154.                 S_shift     = 4'd4,
  155.                 S_down      = 4'd5,
  156.                 S_remove_1  = 4'd6,
  157.                 S_remove_2  = 4'd7,
  158.                 S_isdie     = 4'd8,
  159.                 S_stop      = 4'd9;

  160.     reg [3:0] state, next_state;

  161.     always @(posedge clk or negedge rst_n)
  162.     begin
  163.         if (!rst_n)
  164.             state <= S_idle;
  165.         else
  166.             state <= next_state;
  167.     end



  168.     always @ (posedge clk or negedge rst_n)
  169.     begin
  170.         if (!rst_n)
  171.             time_cnt <= 0;
  172.         else if (hold == 0 && time_cnt < time_val)
  173.             time_cnt <= time_cnt + 1;
  174.         else if (move_down == 1)
  175.             time_cnt <= 0;
  176.         else begin
  177.             time_cnt <= time_cnt;
  178.         end
  179.     end
  180.     always @ (posedge clk or negedge rst_n)
  181.     begin
  182.         if (!rst_n) opcode<=0;
  183.         else opcode<={right, left, down, rotate};
  184.     end


  185.     always @ (*)
  186.     begin
  187.         next_state = S_idle;
  188.         hold = 1;
  189.         gen_random = 0;
  190.         //opcode = 4'b0000;
  191.         shift = 0;
  192.         move_down = 0;
  193.         remove_1 = 0;
  194.         remove_2 = 0;
  195.         stop = 0;
  196.         move = 0;
  197.         isdie = 0;
  198.         case (state)
  199.         S_idle:
  200.         begin
  201.             if (start)
  202.                 next_state = S_new;
  203.             else
  204.                 next_state = S_idle;
  205.         end
  206.         S_new:
  207.         begin
  208.             gen_random = 1;
  209.             next_state = S_hold;
  210.         end
  211.         S_hold:
  212.         begin
  213.             hold = 0;
  214.             if (time_cnt == time_val)
  215.             begin
  216.                 next_state = S_down;
  217.             end
  218.             else if ((down_reg == 0) && (down == 1))
  219.             begin
  220.                next_state = S_down;
  221.             end
  222.             else if ((left_reg == 0 && left == 1)|| ( right_reg == 0 && right == 1)||(up_reg == 0 && rotate == 1))
  223.             begin
  224.                 next_state = S_move;
  225.             end
  226.             else
  227.                 next_state = S_hold;
  228.         end
  229.         S_move:
  230.         begin
  231.             move = 1;
  232.             if (move_comp)
  233.                 next_state = S_shift;
  234.             else
  235.                 next_state = S_hold;
  236.         end
  237.         S_shift:
  238.         begin
  239.             shift = 1;
  240.             next_state = S_hold;
  241.         end
  242.         S_down:
  243.         begin
  244.             move_down = 1;
  245.             if (down_comp)
  246.                 next_state = S_shift;
  247.             else
  248.                 next_state = S_remove_1;               
  249.         end
  250.         S_remove_1:
  251.         begin
  252.             remove_1 = 1;
  253.             next_state = S_remove_2;
  254.         end
  255.         S_remove_2:
  256.         begin
  257.             remove_2 = 1;
  258.             if (remove_2_finish)
  259.                 next_state = S_isdie;
  260.             else
  261.                 next_state = S_remove_2;
  262.         end
  263.         S_isdie:
  264.         begin
  265.             isdie = 1;
  266.             if (die == 1)
  267.                 next_state = S_stop;
  268.             else
  269.                 next_state = S_new;
  270.         end
  271.         S_stop:
  272.         begin
  273.             stop = 1;
  274.             next_state = S_idle;
  275.         end
  276.         default next_state = S_idle;
  277.         endcase
  278.     end

  279. endmodule



  280. 数据路径
  281. module Datapath_Unit #(
  282.     parameter   A_1 = 7'b0001000,
  283.                 B_1 = 7'b0011000,
  284.                 B_2 = 7'b0010100,
  285.                 B_3 = 7'b0010010,
  286.                 B_4 = 7'b0010001,
  287.                 C_1 = 7'b0101000,
  288.                 C_2 = 7'b0100100,
  289.                 C_3 = 7'b0100010,
  290.                 C_4 = 7'b0100001,
  291.                 D_1 = 7'b0111000,
  292.                 D_2 = 7'b0110100,
  293.                 E_1 = 7'b1001000,
  294.                 E_2 = 7'b1000100,
  295.                 E_3 = 7'b1000010,
  296.                 E_4 = 7'b1000001,
  297.                 F_1 = 7'b1011000,
  298.                 F_2 = 7'b1010100,
  299.                 G_1 = 7'b1101000,
  300.                 G_2 = 7'b1100100
  301.     )(
  302.     output reg MOVE_ABLE,SHIFT_FINISH,DOWN_ABLE,DIE_TRUE,
  303.     output     [239:0] M_OUT,
  304.     output reg [4:0] n,
  305.     output reg [3:0] m,
  306.     output reg [6:0] BLOCK,
  307.     //output reg REMOVE_1_FINISH,
  308.     output reg REMOVE_2_FINISH,
  309.     //output reg NEW_BLOCK,

  310.     input clk,rst_n,MOVE,DOWN,DIE,SHIFT,REMOVE_1,REMOVE_2,NEW,STOP,AUTODOWN,
  311.     input [3:0] KEYBOARD
  312.     );
复制代码

全部资料51hei下载地址:
Verilog设计.7z (7.57 MB, 下载次数: 24)

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:306262 发表于 2021-4-6 09:51 | 显示全部楼层
牛逼PLUS!感谢楼主分享!
回复

使用道具 举报

ID:866849 发表于 2022-6-23 15:06 | 显示全部楼层
这个是完成了的吗
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表