今天給各位大俠帶來基于FPGA的“俄羅斯方塊”設計。
設計目的
通過此次項目,完成以下目的:
1)?熟悉Xilinx FPGA的架構及開發流程;
2)?設計一個功能完整的系統,掌握FSM + Datapath的設計方法。
設計內容
1.?項目介紹
本項目主要在FPGA上實現了一個經典小游戲“俄羅斯方塊”。本項目基本解決方案是,使用Xilinx Zynq系列開發板 ZedBoard 作為平臺,實現主控模塊,通過VGA接口來控制屏幕進行顯示。
2.?系統框架
整個系統由四部分組成,按鍵輸入處理模塊、控制模塊、數據路徑模塊以及VGA顯示接口模塊。整個系統的結構如下圖所示:
下面分別對四個模塊進行介紹:
1)?按鍵輸入處理模塊
按鍵處理模塊的主要功能是對輸入系統的up,down,left,right四個控制信號進行消抖處理,并對其進行上升沿檢測。
消抖模塊采用了一個4位的移位寄存器,先將輸入信號延遲4個時鐘周期,再對其以一個較低的時鐘頻率進行采用。消抖模塊的結構如下圖所示:
為了簡化控制系統,在本系統的設計過程中,不考慮長時間按鍵產生連按效果。因而,需要對按鍵進行上升沿檢測。上升沿檢測的基本實現方案是加入一組寄存器,對前一個的按鍵信號進行暫存,將暫存的值與當前值進行比較,當上一個值為0而當前值為1時,即認為其檢測到了一個上升沿。?
2)?控制模塊
控制模塊采用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)?數據路徑
數據路徑模塊主要功能是,根據控制模塊給出的信號,對俄羅斯方塊當前的邏輯狀態進行判斷,更新背景矩陣。具體如下:
方塊:
方塊分為非活動方塊與活動方塊。非活動方塊為:(1)之前下落的方塊;(2)下落后方塊消除之后的結果。由背景矩陣表示?;顒臃綁K為當前下落中的方塊,由活動方塊坐標與方塊類型表示(后簡稱方塊)。
背景矩陣:
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種,如下圖:(圖中,深色方塊是該種方塊的固定點)
方塊運動:
產生:
方塊產生由一個簡單的偽隨機過程決定。系統采用一個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]不為空,游戲結束。
4)?顯示部分
輸出結果通過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個像素,其分配如下:
同樣的,一個完整的垂直同步信號也分為四個區域,總共525個像素,分配如下:
模塊通過組織輸出行掃描信號、列掃描信號和三原色信號對顯示屏實現控制。
設計結果
設計結果圖如下:
圖7:設計結果圖
設計代碼
由于代碼量較大,這里只展示了部分代碼,需要的大俠可以按照開篇介紹的方法進入“FPGA技術江湖”知識星球獲取設計文檔,獲取設計代碼。
頂層模塊設計代碼:
?
?
`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 / 1ps module 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
?
?
數據路徑以及VGA等模塊在這里就不展示,代碼量過大,詳情見開篇介紹。
審核編輯:黃飛
?
評論
查看更多