加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 設(shè)計目的
    • 設(shè)計內(nèi)容
    • 設(shè)計結(jié)果
    • 設(shè)計代碼
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

基于FPGA的“俄羅斯方塊”系統(tǒng)設(shè)計

03/28 12:22
3904
閱讀需 32 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

大俠好,歡迎來到FPGA技術(shù)江湖,江湖偌大,相見即是緣分。大俠可以關(guān)注FPGA技術(shù)江湖,在“闖蕩江湖”、"行俠仗義"欄里獲取其他感興趣的資源,或者一起煮酒言歡。

今天給各位大俠帶來基于FPGA的“俄羅斯方塊”設(shè)計。

設(shè)計目的

通過此次項目,完成以下目的:

1)?熟悉Xilinx FPGA的架構(gòu)及開發(fā)流程;

2)?設(shè)計一個功能完整的系統(tǒng),掌握FSM + Datapath的設(shè)計方法。

設(shè)計內(nèi)容

1.?項目介紹

本項目主要在FPGA上實現(xiàn)了一個經(jīng)典小游戲“俄羅斯方塊”。本項目基本解決方案是,使用Xilinx Zynq系列開發(fā)板 ZedBoard 作為平臺,實現(xiàn)主控模塊,通過VGA接口來控制屏幕進(jìn)行顯示。

2.?系統(tǒng)框架

整個系統(tǒng)由四部分組成,按鍵輸入處理模塊、控制模塊、數(shù)據(jù)路徑模塊以及VGA顯示接口模塊。整個系統(tǒng)的結(jié)構(gòu)如下圖所示:

下面分別對四個模塊進(jìn)行介紹:

1)?按鍵輸入處理模塊

按鍵處理模塊的主要功能是對輸入系統(tǒng)的up,down,left,right四個控制信號進(jìn)行消抖處理,并對其進(jìn)行上升沿檢測。

消抖模塊采用了一個4位的移位寄存器,先將輸入信號延遲4個時鐘周期,再對其以一個較低的時鐘頻率進(jìn)行采用。消抖模塊的結(jié)構(gòu)如下圖所示:

為了簡化控制系統(tǒng),在本系統(tǒng)的設(shè)計過程中,不考慮長時間按鍵產(chǎn)生連按效果。因而,需要對按鍵進(jìn)行上升沿檢測。上升沿檢測的基本實現(xiàn)方案是加入一組寄存器,對前一個的按鍵信號進(jìn)行暫存,將暫存的值與當(dāng)前值進(jìn)行比較,當(dāng)上一個值為0而當(dāng)前值為1時,即認(rèn)為其檢測到了一個上升沿。

2)?控制模塊

控制模塊采用FSM的方式進(jìn)行控制。在控制模塊中,定義了10個狀態(tài):

S_idle:上電復(fù)位后進(jìn)入空狀態(tài),當(dāng)start信號為1時進(jìn)入S_new狀態(tài)

S_new:用于產(chǎn)生新的俄羅斯方塊。

S_hold:保持狀態(tài)。在這個狀態(tài)中進(jìn)行計時,當(dāng)時間到達(dá)一定間隔時,轉(zhuǎn)到S_down狀態(tài);或者等待輸入信號(up,down,left,right)時,轉(zhuǎn)到S_down(按鍵為down)或者S_move(up,left,right)狀態(tài)。

S_down:判斷當(dāng)前俄羅斯塊能否下移一格。如果可以,則轉(zhuǎn)到S_remove_1狀態(tài),如果不行,則轉(zhuǎn)到S_shift狀態(tài)。

S_move:判斷當(dāng)前俄羅斯塊能夠按照按鍵信號指定的指令進(jìn)行移動,如果可以,則轉(zhuǎn)到S_shift狀態(tài),如果不可以,則轉(zhuǎn)到S_remove_1狀態(tài)。

S_shift:更新俄羅斯方塊的坐標(biāo)信息。返回S_hold。

S_remove_1:更新整個屏幕的矩陣信息。轉(zhuǎn)移到S_remove_2狀態(tài)。

S_remove_2:判斷是否可以消除,將可以消除的行消除,并將上面的行下移一行。重復(fù)此過程,直到?jīng)]有可消除的行為止。跳轉(zhuǎn)到S_isdie狀態(tài)

S_isdie:判斷是否游戲結(jié)束。如果結(jié)束,則跳轉(zhuǎn)到S_stop狀態(tài)。如果沒有,則跳轉(zhuǎn)到S_new狀態(tài),生成新的俄羅斯方塊。

S_stop:清除整個屏幕,并跳轉(zhuǎn)到S_idle狀態(tài)。

整個控制過程的ASMD圖如下圖所示:

3)?數(shù)據(jù)路徑

數(shù)據(jù)路徑模塊主要功能是,根據(jù)控制模塊給出的信號,對俄羅斯方塊當(dāng)前的邏輯狀態(tài)進(jìn)行判斷,更新背景矩陣。具體如下:

方塊:

方塊分為非活動方塊與活動方塊。非活動方塊為:(1)之前下落的方塊;(2)下落后方塊消除之后的結(jié)果。由背景矩陣表示?;顒臃綁K為當(dāng)前下落中的方塊,由活動方塊坐標(biāo)與方塊類型表示(后簡稱方塊)。

背景矩陣:

reg [9:0] R [23:0];

背景矩陣R是24行10列的寄存器組,負(fù)責(zé)保存非活動方塊坐標(biāo),即R中任一位置,如方塊存在,則該位置1,否則為0。

活動方塊坐標(biāo):

output reg [4:0] n,

output reg [3:0] m,

n, m分別為當(dāng)前活動方塊的行、列指針,指向方塊固定點(diǎn)位置。方塊固定點(diǎn)為方塊旋轉(zhuǎn)時不變的格點(diǎn),依據(jù)方塊種類決定,下文方塊模型中詳述。

方塊類型:

output reg [6:0] BLOCK,

BLOCK代表方塊類型,由7位編碼構(gòu)成。

數(shù)據(jù)交換:

Datapath與其余模塊的數(shù)據(jù)交換分為兩部分:

(1)與control_unit間的狀態(tài)指令交互;

(2)控制merge,間接實現(xiàn)對VGA的控制。

方塊模型:

俄羅斯方塊共有7種形狀的方塊(O,L,J,I,T,Z,S),每種方塊有1-4種不同的旋轉(zhuǎn)變形方式。為方便起見,將方塊定位A-G,旋轉(zhuǎn)編號為1-4,將方塊編碼成A_1-G_2的19種,如下圖:(圖中,深色方塊是該種方塊的固定點(diǎn))

方塊運(yùn)動:

產(chǎn)生:

方塊產(chǎn)生由一個簡單的偽隨機(jī)過程決定。系統(tǒng)采用一個3位的計數(shù)器產(chǎn)生隨機(jī)數(shù),進(jìn)入S_new,BLOCK的值被NEW_BLOCK覆蓋,方塊坐標(biāo)n<=1;m<=5;同時,根據(jù)計數(shù)器,NEW_BLOCK的值刷新為A_1,B_1,…,G_1中的一種,作為下一次方塊。

移動:

方塊移動分為四種:旋轉(zhuǎn),下落,向左,向右,由鍵盤KEYBOARD=[UP, DOWN, LEFT, RIGHT]控制。移動分兩步進(jìn)行:(1)判斷;(2)轉(zhuǎn)換。

判斷過程包含S_down,S_move。判斷分兩步:首先,判斷變換后方塊坐標(biāo)是否合法,即變換后是否會造成方塊越界。然后,判斷變換后方塊可能占據(jù)的新位置是否有背景矩陣方塊存在。兩步判斷通過后返回成功信號,否則失敗。因判斷代碼量較多,僅舉一例說明:

判斷D_1向右運(yùn)動(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;

轉(zhuǎn)換過程(S_shift)進(jìn)行方塊的移動或變形。根據(jù)KEYBOARD,移動時,改變方塊坐標(biāo);變形時,方塊按類別變換,如:A_1→A_1;B_1→B_2; B_2→B_3; B_4→B_1;

停止與消除:

方塊停止與消除由兩個狀態(tài)完成:S_remove1,S_remove2。

前一狀態(tài)中,根據(jù)BLOCK, n, m,將活動方塊位置覆蓋至R,變?yōu)榉腔顒臃綁K。

后一狀態(tài)中,根據(jù)行滿狀態(tài),進(jìn)行行的消除與平移,具體如下:

顯然,俄羅斯方塊能影響的最大行數(shù)為4,因此,在REMOVE_2中,僅對R[n-1],R[n],R[n+1],R[n+2]四行依次進(jìn)行處理。處理過程為:如果該行(k)滿,則由k行開始,至1行結(jié)束,逐行向下平移,當(dāng)前平移位置由計數(shù)器REMOVE_2_C控制,當(dāng)前行消除截止由標(biāo)志位SIG確認(rèn)。

每行處理完后,將REMOVE_FINISH[3:0]中相應(yīng)位置1,REMOVE_FINISH全1時,REMOVE_2完成。

死亡判定:

R中的0-3行位于屏幕上方,不進(jìn)行顯示,僅有新生成的方塊坐標(biāo)會進(jìn)入這一區(qū)域。因而,當(dāng)消除完成后,如R[3]不為空,游戲結(jié)束。

4)?顯示部分

輸出結(jié)果通過VGA接口接入顯示屏顯示。VGA(Video Graphics Array)視頻圖形陣列是IBM于1987年提出的一個使用模擬信號的電腦顯示標(biāo)準(zhǔn)。VGA接口即電腦采用VGA標(biāo)準(zhǔn)輸出數(shù)據(jù)的專用接口。VGA接口共有15針,分成3排,每排5個孔,顯卡上應(yīng)用最為廣泛的接口類型,絕大多數(shù)顯卡都帶有此種接口。它傳輸紅、綠、藍(lán)模擬信號以及同步信號(水平和垂直信號)。

使用Verilog HDL語言對VGA進(jìn)行控制一般只需控制行掃描信號、列掃描信號和紅綠藍(lán)三色信號輸出即可。

VGA輸出可分為四個模塊:時鐘分頻模塊、數(shù)據(jù)組織模塊、接口控制模塊和頂層模塊。以下進(jìn)行分塊描述。

時鐘模塊分頻模塊對FPGA系統(tǒng)時鐘進(jìn)行分頻。由于使用的顯示屏參數(shù)為640*480*60Hz,其真實屏幕大小為800*525,因此所需時鐘頻率為800*525*60Hz=25.175MHz,可近似處理為25MHz。FPGA系統(tǒng)時鐘為100M,因此將其四分頻即可基本滿足顯示要求。

數(shù)據(jù)組織模塊是將預(yù)備輸出的數(shù)據(jù)組織為可以通過VGA接口控制的數(shù)據(jù)形式,本次設(shè)計中因接口已經(jīng)協(xié)調(diào),數(shù)據(jù)可不經(jīng)過此模塊進(jìn)行組織,故可忽略該模塊。

接口控制模塊通過VGA接口對顯示屏進(jìn)行控制。VGA的掃描順序是從左到右,從上到下。例如在640X480的顯示模式下,從顯示器的左上角開始往右掃描,直到640個像素掃完,再回到最左邊,開始第二行的掃描,如此往復(fù),到第480行掃完時即完成一幀圖像的顯示。這時又回到左上角,開始下一幀圖像的掃描。如果每秒能完成60幀,則稱屏幕刷新頻率為60Hz。宏觀上,一幀屏幕由480個行和640個列填充而成,而實際上,一幀屏幕除了顯示區(qū),還包含其他未顯示部分,作為邊框或者用來同步。具體而言,一個完整的行同步信號包含了左邊框、顯示區(qū)、右邊框還有返回區(qū)四個部分,總共800個像素,其分配如下:

同樣的,一個完整的垂直同步信號也分為四個區(qū)域,總共525個像素,分配如下:

模塊通過組織輸出行掃描信號、列掃描信號和三原色信號對顯示屏實現(xiàn)控制。

設(shè)計結(jié)果

設(shè)計結(jié)果圖如下:

圖7:設(shè)計結(jié)果圖

設(shè)計代碼

由于代碼量較大,這里只展示了部分代碼,需要的大俠可以按照開篇介紹的方法進(jìn)入“FPGA技術(shù)江湖”知識星球獲取設(shè)計文檔,獲取設(shè)計代碼。

頂層模塊設(shè)計代碼:

`timescale?1ns?/?1ps//////////////////////////////////////////////////////////////////////////////////// Company: // Engineer: // // Create Date: // Design Name: // Module Name: tetris// Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision:// Revision 0.01 - File Created// Additional Comments:// //////////////////////////////////////////////////////////////////////////////////

module tetris #(    parameter ROW = 20,    parameter COL = 10    )(    input clk,    input rst,    input UP_KEY,    input LEFT_KEY,    input RIGHT_KEY,    input DOWN_KEY,    input start,    output vsync_r,    output hsync_r,    output [3:0]OutRed, OutGreen,    output [3:0]OutBlue    );        wire [3:0] opcode;    wire gen_random;    wire hold;    wire shift;    wire move_down;    wire remove_1;    wire remove_2;    wire stop;    wire move;    wire isdie;    wire shift_finish;    wire down_comp;    wire move_comp;    wire die;    wire [ROW*COL-1:0] data_out;        wire [6:0] BLOCK;    wire [3:0] m;    wire [4:0] n;    wire [(ROW+4)*COL-1:0] M_OUT;        wire rotate;    wire left;    wire right;    wire down;    wire auto_down;    wire rst_n;    assign rst_n = ~rst;        key u_key (        .clk(clk),        .rst_n(rst_n),        .UP_KEY(UP_KEY),        .LEFT_KEY(LEFT_KEY),        .RIGHT_KEY(RIGHT_KEY),        .DOWN_KEY(DOWN_KEY),        .rotate(rotate),        .left(left),        .right(right),        .down(down)    );        game_control_unit u_Controller (        .clk(clk),        .rst_n(rst_n),        .rotate(rotate),        .left(left),        .right(right),        .down(down),        .start(start),        .opcode(opcode),        .gen_random(gen_random),????????.hold(hold),???????? .shift(shift),        .move_down(move_down),        .remove_1(remove_1),        .remove_2(remove_2),        .stop(stop),        .move(move),        .isdie(isdie),        .shift_finish(shift_finish),        .down_comp(down_comp),        .move_comp(move_comp),        .die(die),        .auto_down(auto_down),        .remove_2_finish(remove_2_finish)        );            Datapath_Unit u_Datapath (        .clk(clk),        .rst_n(rst_n),        .NEW(gen_random),        .MOVE(move),        .DOWN(move_down),        .DIE(isdie),        .SHIFT(shift),        .REMOVE_1(remove_1),        .REMOVE_2(remove_2),        .KEYBOARD(opcode),        .MOVE_ABLE(move_comp),        .SHIFT_FINISH(shift_finish),        .DOWN_ABLE(down_comp),        .DIE_TRUE(die),        .M_OUT(M_OUT),        .n(n),        .m(m),        .BLOCK(BLOCK),        .REMOVE_2_FINISH(remove_2_finish),        .STOP(stop),        .AUTODOWN(auto_down)        );        ????merge?u_merge?(????.clk(clk),        .rst_n(rst_n),        .data_in(M_OUT),        .shape(BLOCK),        .x_pos(m),        .y_pos(n),        .data_out(data_out)                );         top u_VGA (        .clk(clk),        .rst(rst),        .number(data_out),        .hsync_r(hsync_r),        .vsync_r(vsync_r),        .OutRed(OutRed),        .OutGreen(OutGreen),        .OutBlue(OutBlue)            );        endmodule

 

KeyBoard模塊代碼:

`timescale 1ns / 1psmodule key(    input clk,    input rst_n,    input UP_KEY,    input LEFT_KEY,    input RIGHT_KEY,    input DOWN_KEY,    output reg rotate,    output reg left,    output reg right,    output reg down    );        reg [3:0] shift_up;    reg [3:0] shift_left;    reg [3:0] shift_right;    reg [3:0] shift_down;        always @(posedge clk or negedge rst_n)    begin        if (!rst_n)            shift_up <= 0;        else            shift_up <= {shift_up[2:0], UP_KEY};    end        always @(posedge clk or negedge rst_n)    begin        if (!rst_n)            shift_right <= 0;        else            shift_right <= {shift_right[2:0], RIGHT_KEY};    end         always @(posedge clk or negedge rst_n)    begin        if (!rst_n)            shift_left <= 0;        else            shift_left <= {shift_left[2:0], LEFT_KEY};    end        always @(posedge clk or negedge rst_n)    begin        if (!rst_n)            shift_down <= 0;        else            shift_down <= {shift_down[2:0], DOWN_KEY};    end

    reg clk_div;    reg [7:0] clk_cnt;    always @ (posedge clk or negedge rst_n)    begin        if (!rst_n)        begin            clk_cnt <= 0;            clk_div <= 0;        end        else if (clk_cnt <= 8'd49)        begin            clk_cnt <= clk_cnt + 1;            clk_div <= clk_div;        end        else        begin            clk_cnt <= 0;            clk_div <= ~clk_div;             end      end
      always @(posedge clk_div or negedge rst_n)      begin          if (!rst_n)          begin              rotate <= 0;              left <= 0;              right <= 0;              down <= 0;          end          else          begin              rotate <= shift_up[3];              left <= shift_left[3];              right <= shift_right[3];              down <= shift_down[3];          end      end       endmodule

 

控制模塊代碼:

module game_control_unit (    input clk,    input rst_n,    input rotate,    input left,    input right,    input down,    input start,    output reg [3:0] opcode,    output reg gen_random,    output reg hold,    output reg shift,    output reg move_down,    output reg remove_1,    output reg remove_2,    output reg stop,    output reg move,    output reg isdie,    output reg auto_down,    input shift_finish,    input remove_2_finish,    input down_comp,    input move_comp,    input die    );        reg left_reg;    reg right_reg;    reg up_reg;    reg down_reg;        always @(posedge clk or negedge rst_n)    begin        if (!rst_n)        begin            left_reg <= 0;            right_reg <= 0;            up_reg <= 0;            down_reg <= 0;        end        else        begin            left_reg <= left;            right_reg <= right;            up_reg <= rotate;            down_reg <=  down;        end    end        reg auto_down_reg;        always @ (posedge clk or negedge rst_n)    begin        if (!rst_n)            auto_down_reg <= 0;        else if (time_cnt == time_val)            auto_down_reg <= 1;        else             auto_down_reg <= 0;    end    always @ (posedge clk or negedge rst_n)    begin        if (!rst_n)            auto_down <= 0;        else            auto_down <= auto_down_reg;    end        parameter time_val = 26'd25000001;    reg [25:0] time_cnt;
    localparam  S_idle      = 4'd0,                S_new       = 4'd1,                S_hold      = 4'd2,                S_move      = 4'd3,                S_shift     = 4'd4,                S_down      = 4'd5,                S_remove_1  = 4'd6,                S_remove_2  = 4'd7,                S_isdie     = 4'd8,                S_stop      = 4'd9;
    reg [3:0] state, next_state;
    always @(posedge clk or negedge rst_n)    begin        if (!rst_n)            state <= S_idle;        else            state <= next_state;    end    

    always @ (posedge clk or negedge rst_n)    begin        if (!rst_n)            time_cnt <= 0;            else if (hold == 0 && time_cnt < time_val)            time_cnt <= time_cnt + 1;        else if (move_down == 1)            time_cnt <= 0;        else begin            time_cnt <= time_cnt;        end    end    always @ (posedge clk or negedge rst_n)    begin        if (!rst_n) opcode<=0;        else opcode<={right, left, down, rotate};    end         
    always @ (*)    begin        next_state = S_idle;        hold = 1;        gen_random = 0;        //opcode = 4'b0000;        shift = 0;        move_down = 0;        remove_1 = 0;        remove_2 = 0;        stop = 0;        move = 0;        isdie = 0;        case (state)        S_idle:        begin            if (start)                next_state = S_new;            else                next_state = S_idle;        end        S_new:        begin            gen_random = 1;            next_state = S_hold;        end                S_hold:        begin            hold = 0;            if (time_cnt == time_val)            begin                next_state = S_down;            end            else if ((down_reg == 0) && (down == 1))            begin               next_state = S_down;            end            else if ((left_reg == 0 && left == 1)|| ( right_reg == 0 && right == 1)||(up_reg == 0 && rotate == 1))            begin                next_state = S_move;            end            else                next_state = S_hold;        end        S_move:        begin            move = 1;            if (move_comp)                next_state = S_shift;            else                next_state = S_hold;        end        S_shift:        begin            shift = 1;            next_state = S_hold;        end        S_down:        begin            move_down = 1;            if (down_comp)                next_state = S_shift;            else????????????????next_state?=?S_remove_1;????????????????        end        S_remove_1:        begin            remove_1 = 1;            next_state = S_remove_2;        end        S_remove_2:        begin            remove_2 = 1;            if (remove_2_finish)                next_state = S_isdie;            else                next_state = S_remove_2;        end        S_isdie:        begin            isdie = 1;            if (die == 1)                next_state = S_stop;            else                next_state = S_new;        end        S_stop:        begin            stop = 1;            next_state = S_idle;        end        default next_state = S_idle;        endcase    end    endmodule

數(shù)據(jù)路徑以及VGA等模塊在這里就不展示,代碼量過大,詳情見開篇介紹。

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風(fēng)險等級 參考價格 更多信息
EPM2210F256C5N 1 Altera Corporation Flash PLD, 11.2ns, 1700-Cell, CMOS, PBGA256, 17 X 17 MM, 1 MM PITCH, LEAD FREE, FBGA-256
$423.74 查看
M1A3P250-FG144I 1 Microsemi FPGA & SoC Field Programmable Gate Array, 6144 CLBs, 250000 Gates, 350MHz, 6144-Cell, CMOS, PBGA144, 13X 13 MM, 1.45 MM HEIGHT, 1 MM PITCH, FBGA-144
暫無數(shù)據(jù) 查看
A3P250-FG144 1 Microsemi FPGA & SoC Field Programmable Gate Array, 250000 Gates, CMOS, PBGA144, 1 MM PITCH, FBGA-144
$21.67 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

任何技術(shù)的學(xué)習(xí)就好比一個江湖,對于每一位俠客都需要不斷的歷練,從初入江湖的小白到歸隱山林的隱世高人,需要不斷的自我感悟自己修煉,讓我們一起仗劍闖FPGA乃至更大的江湖。