標(biāo)題: 俄羅斯方塊Verilog HDL程序設(shè)計 [打印本頁]
作者: zxopenljx 時間: 2021-3-19 17:37
標(biāo)題: 俄羅斯方塊Verilog HDL程序設(shè)計
通過此次項目,完成以下目的:
- 熟悉Xilinx FPGA的架構(gòu)及開發(fā)流程
- 設(shè)計一個功能完整的系統(tǒng),掌握FSM + Datapath的設(shè)計方法。
實驗內(nèi)容本項目主要在FPGA上實現(xiàn)了一個經(jīng)典小游戲“俄羅斯方塊”。本項目基本解決方案是,使用Xilinx Zynq系列開發(fā)板ZedBoard作為平臺,實現(xiàn)主控模塊,通過VGA接口來控制屏幕進行顯示。
整個系統(tǒng)由四部分組成,按鍵輸入處理模塊、控制模塊、數(shù)據(jù)路徑模塊以及VGA顯示接口模塊。整個系統(tǒng)的結(jié)構(gòu)如下圖所示:
圖1:系統(tǒng)框圖
下面分別對四個模塊進行介紹:
按鍵處理模塊的主要功能是對輸入系統(tǒng)的up,down,left,right四個控制信號進行消抖處理,并對其進行上升沿檢測。
消抖模塊采用上課所提出的結(jié)構(gòu),采用了一個4位的移位寄存器,先將輸入信號延遲4個時鐘周期,再對其以一個較低的時鐘頻率進行采用。消抖模塊的結(jié)構(gòu)如下圖所示:
圖2:消抖模塊結(jié)構(gòu)示意圖
為了簡化控制系統(tǒng),在本系統(tǒng)的設(shè)計過程中,不考慮長時間按鍵產(chǎn)生連按效果。因而,需要對按鍵進行上升沿檢測。上升沿檢測的基本實現(xiàn)方案是加入一組寄存器,對前一個的按鍵信號進行暫存,將暫存的值與當(dāng)前值進行比較,當(dāng)上一個值為0而當(dāng)前值為1時,即認為其檢測到了一個上升沿。
控制模塊采用FSM的方式進行控制。在控制模塊中,定義了10個狀態(tài):
S_idle:上電復(fù)位后進入的空狀態(tài),當(dāng)start信號為1時進入S_new狀態(tài)
S_new:用于產(chǎn)生新的俄羅斯方塊。
S_hold:保持狀態(tài)。在這個狀態(tài)中進行計時,當(dāng)時間到達一定間隔時,轉(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)前俄羅斯塊能夠按照按鍵信號指定的指令進行移動,如果可以,則轉(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: 控制模塊ASMD圖
數(shù)據(jù)路徑模塊主要功能是,根據(jù)控制模塊給出的信號,對俄羅斯方塊當(dāng)前的邏輯狀態(tài)進行判斷,更新背景矩陣。具體如下:
方塊:
方塊分為非活動方塊與活動方塊。非活動方塊為:(1)之前下落的方塊;(2)下落后方塊消除之后的結(jié)果。由背景矩陣表示。活動方塊為當(dāng)前下落中的方塊,由活動方塊坐標(biāo)與方塊類型表示(后簡稱方塊)。
背景矩陣:
reg [9:0] R [23:0];
背景矩陣R是24行10列的寄存器組,負責(zé)保存非活動方塊坐標(biāo),即R中任一位置,如方塊存在,則該位置1,否則為0。
活動方塊坐標(biāo):
output reg [4:0] n,
output reg [3:0] m,
n, m分別為當(dāng)前活動方塊的行、列指針,指向方塊固定點位置。方塊固定點為方塊旋轉(zhuǎ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種,如 下圖:
圖中,深色方塊是該種方塊的固定點。
圖4: 方塊模型示意圖
方塊運動:
產(chǎn)生:
方塊產(chǎn)生由一個簡單的偽隨機過程決定。系統(tǒng)采用一個3位的計數(shù)器產(chǎn)生隨機數(shù),進入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]控制。移動分兩步進行:(1)判斷;(2)轉(zhuǎn)換。
判斷過程包含S_down,S_move。判斷分兩步:首先,判斷變換后方塊坐標(biāo)是否合法,即變換后是否會造成方塊越界。然后,判斷變換后方塊可能占據(jù)的新位置是否有背景矩陣方塊存在。兩步判斷通過后返回成功信號,否則失敗。因判斷代碼量較多,僅舉一例說明:
判斷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;
轉(zhuǎn)換過程(S_shift)進行方塊的移動或變形。根據(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),進行行的消除與平移,具體如下:
顯然,俄羅斯方塊能影響的最大行數(shù)為4,因此,在REMOVE_2中,僅對R[n-1],R[n],R[n+1],R[n+2]四行依次進行處理。處理過程為:如果該行(k)滿,則由k行開始,至1行結(jié)束,逐行向下平移,當(dāng)前平移位置由計數(shù)器REMOVE_2_C控制,當(dāng)前行消除截止由標(biāo)志位SIG確認。
每行處理完后,將REMOVE_FINISH[3:0]中相應(yīng)位置1,REMOVE_FINISH全1時,REMOVE_2完成。
死亡判定:
R中的0-3行位于屏幕上方,不進行顯示,僅有新生成的方塊坐標(biāo)會進入這一區(qū)域。因而,當(dāng)消除完成后,如R[3]不為空,游戲結(jié)束。
輸出結(jié)果通過VGA接口接入顯示屏顯示。VGA(Video Graphics Array)視頻圖形陣列是IBM于1987年提出的一個使用模擬信號的電腦顯示標(biāo)準。VGA接口即電腦采用VGA標(biāo)準輸出數(shù)據(jù)的專用接口。VGA接口共有15針,分成3排,每排5個孔,顯卡上應(yīng)用最為廣泛的接口類型,絕大多數(shù)顯卡都帶有此種接口。它傳輸紅、綠、藍模擬信號以及同步信號(水平和垂直信號)。
使用Verilog HDL語言對VGA進行控制一般只需控制行掃描信號、列掃描信號和紅綠藍三色信號輸出即可。
VGA輸出可分為四個模塊:時鐘分頻模塊、數(shù)據(jù)組織模塊、接口控制模塊和頂層模塊。以下進行分塊描述。
時鐘模塊分頻模塊對FPGA系統(tǒng)時鐘進行分頻。由于使用的顯示屏參數(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)過此模塊進行組織,故可忽略該模塊。
接口控制模塊通過VGA接口對顯示屏進行控制。VGA的掃描順序是從左到右,從上到下。例如在640X480的顯示模式下,從顯示器的左上角開始往右掃描,直到640個像素掃完,再回到最左邊,開始第二行的掃描,如此往復(fù),到第480行掃完時即完成一幀圖像的顯示。這時又回到左上角,開始下一幀圖像的掃描。如果每秒能完成60幀,則稱屏幕刷新頻率為60Hz。宏觀上,一幀屏幕由480個行和640個列填充而成,而實際上,一幀屏幕除了顯示區(qū),還包含其他未顯示部分,作為邊框或者用來同步。具體而言,一個完整的行同步信號包含了左邊框、顯示區(qū)、右邊框還有返回區(qū)四個部分,總共800個像素,其分配如下:
圖5: VGA行掃描時序
同樣的,一個完整的垂直同步信號也分為四個區(qū)域,總共525個像素,分配如下:
圖6:VGA場掃描時序
模塊通過組織輸出行掃描信號、列掃描信號和三原色信號對顯示屏實現(xiàn)控制。
實驗結(jié)果實驗結(jié)果圖如下:
圖7:實驗結(jié)果圖
實驗總結(jié)本次項目我們完成了既定目標(biāo),即完成一個經(jīng)典小游戲“俄羅斯方塊”的核心功能。在本次實驗過程中,我們通過采用分工合作的方式,通過對系統(tǒng)功能的分析,確定解決方案,完成了對一個系統(tǒng)自上而下的設(shè)計,并嘗試采用控制單元+數(shù)據(jù)路徑這樣的方式來處理核心模塊。
由于時間倉促,加之對俄羅斯方塊邏輯復(fù)雜度估計不足,到最后展示之前我們才完成了對核心模塊的調(diào)試。因此,在用戶界面上沒有做過多的調(diào)整。另外,由于在進行模塊劃分時,一些接口沒有事先定義好,導(dǎo)致在最后系統(tǒng)整合時,不得不進行修改與調(diào)整,由此而造成了一部分時間的浪費。
總的來說,通過這個項目,小組成員對于硬件設(shè)計“并行”的特點有了比較直接的認識,同時也在調(diào)試的過程中掌握了一些硬件調(diào)試常用的方法,也認識到了仿真的重要意義所在。另外就是關(guān)于團隊協(xié)作方面的一個教訓(xùn),在系統(tǒng)劃分時要注意把接口定義好,以免造成不必要的代價。
實驗代碼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
-
-
-
- 數(shù)據(jù)路徑
- module Datapath_Unit #(
- parameter A_1 = 7'b0001000,
- B_1 = 7'b0011000,
- B_2 = 7'b0010100,
- B_3 = 7'b0010010,
- B_4 = 7'b0010001,
- C_1 = 7'b0101000,
- C_2 = 7'b0100100,
- C_3 = 7'b0100010,
- C_4 = 7'b0100001,
- D_1 = 7'b0111000,
- D_2 = 7'b0110100,
- E_1 = 7'b1001000,
- E_2 = 7'b1000100,
- E_3 = 7'b1000010,
- E_4 = 7'b1000001,
- F_1 = 7'b1011000,
- F_2 = 7'b1010100,
- G_1 = 7'b1101000,
- G_2 = 7'b1100100
- )(
- output reg MOVE_ABLE,SHIFT_FINISH,DOWN_ABLE,DIE_TRUE,
- output [239:0] M_OUT,
- output reg [4:0] n,
- output reg [3:0] m,
- output reg [6:0] BLOCK,
- //output reg REMOVE_1_FINISH,
- output reg REMOVE_2_FINISH,
- //output reg NEW_BLOCK,
-
- input clk,rst_n,MOVE,DOWN,DIE,SHIFT,REMOVE_1,REMOVE_2,NEW,STOP,AUTODOWN,
- input [3:0] KEYBOARD
- );
復(fù)制代碼
全部資料51hei下載地址:
Verilog設(shè)計.7z
(7.57 MB, 下載次數(shù): 33)
2021-3-19 18:01 上傳
點擊文件名下載附件
俄羅斯方塊_Verilog課程設(shè)計.rar
下載積分: 黑幣 -5
作者: lzf-1586 時間: 2021-4-6 09:51
牛逼PLUS!感謝樓主分享!
作者: 123456dsa 時間: 2022-6-23 15:06
這個是完成了的嗎
歡迎光臨 (http://www.raoushi.com/bbs/) |
Powered by Discuz! X3.1 |