FPGA-避障游戲

游戲介紹

游戲規(guī)則

利用FPGA矢空,以640*480的分辨率使用VGA顯示航罗,玩家利用按鍵操作位于屏幕左側(cè)的方塊移動,來躲避從屏幕右側(cè)向左邊移動的留有一定間隙的障礙物妇多。

游戲要求

畫面及操作盡量連續(xù)伤哺,游戲結(jié)束時玩家操作的物體變成紅色,按下重新開始后復(fù)位游戲者祖,隨著時間變長加速以提高難度立莉。

基本上整個游戲就像是以前的飛機小游戲,為了增加可玩性七问,我將游戲設(shè)置為方塊自動降落蜓耻,外部只有一個按鍵,實現(xiàn)方塊的向上移動械巡,去躲避向左移動的擋板刹淌。

設(shè)計分析

模塊設(shè)定

首先游戲要有顯示畫面,所以少不了vga的顯示模塊讥耗;其次是要控制方塊的移動有勾,需要鍵盤的輸入模塊,最后是關(guān)于游戲的邏輯控制古程,這需要一個控制模塊蔼卡。

那這些模塊都需要什么哪些信號呢?接著分析下挣磨。

鍵盤模塊

鍵盤模塊作為整個避障游戲系統(tǒng)的輸入雇逞,它主要是為其他模塊提供信號,對信號的處理并不多茁裙。

輸入 功能描述
clk 時鐘
reset 復(fù)位
up 使方塊上升
輸出 功能描述
up_key_press 方塊上升信號
down_key_press 方塊下降信號

前面說了塘砸,為了增加可玩性,所以我在系統(tǒng)內(nèi)部設(shè)置了讓方塊自動下落晤锥,所以輸入只有up,但是輸出時會有down_key_press掉蔬。

關(guān)于鍵盤的輸入輸出就是如此,至于模塊如何實現(xiàn)這些功能矾瘾,分析階段不解釋眉踱,在下文中會陸陸續(xù)續(xù)講解。

VGA模塊

vga模塊的功能就是將數(shù)據(jù)顯示霜威,原理在下文我會講解谈喳,弄懂它的時序后,問題不會太大戈泼,一開始可以嘗試先顯示個彩條之類的婿禽,測試下赏僧,找下感覺。

輸入 功能描述
clk 時鐘
reset 復(fù)位
輸出 功能描述
dat_act 數(shù)據(jù)有效標(biāo)志位
hc 行掃描計數(shù)器
vc 列掃描計數(shù)器
hsync 行同步信號
vsync 場同步信號

控制模塊

控制模塊這個部分是整個游戲的規(guī)則的設(shè)定扭倾,可以說淀零,游戲怎么玩完全由這個模塊決定,根據(jù)游戲的描述和要求膛壹,我們是要控制一個方塊去躲避不斷向左移動的擋板驾中,所以這個擋板和方塊怎么“弄出來”就是關(guān)鍵了。

怎么弄出來呢?

首先要有個概念模聋,我們所看到的VGA圖像都是一個個像素點組成肩民,使用640*480 的顯示模式,這個規(guī)定相當(dāng)于為我們規(guī)定了橫縱坐標(biāo)的定義域链方,在這個二維屏幕上持痰。方塊和遮擋板就可以用數(shù)學(xué)式子“畫”出來,例如邊長為兩個像素點一個方塊就是:0<x<2 ,0<y<2祟蚀。

友情提醒:FPAG中能用正數(shù)就盡量不要用負(fù)數(shù)工窍,數(shù)字在FPGA中是用補碼表示的,負(fù)數(shù)的補碼往往與我們的思維邏輯有點出入前酿,容易導(dǎo)致出錯患雏。

輸入 功能描述
clk 時鐘
reset 復(fù)位
up_key_press 方塊上升信號
down_key_press 方塊的下降信號
hc 行掃描計數(shù)器
vc 列掃描計數(shù)器
dat_act 數(shù)據(jù)有效標(biāo)志位(用于消隱)
輸出 功能描述
disp_RGB 顯示所需的數(shù)據(jù)

總體的設(shè)計圖

image

方案設(shè)計

鍵盤模塊

鍵盤模塊的要點在于消抖,和控制方塊移動罢维。

消抖

無論是什么器件淹仑,鍵盤的消抖都是老套路,分為硬件消抖和軟件消抖言津,硬件消抖如使用RS觸發(fā)器實現(xiàn)或者是加電容實現(xiàn)攻人,一般是制作板子的時候考慮加上去的取试,平時我們使用現(xiàn)成的板子悬槽,大多數(shù)都是使用軟件消抖。

鍵盤產(chǎn)生抖動是機械特性瞬浓,在我們按下按鍵是接觸點的電壓波形大致如下圖:

image

從圖可以看出初婆,按下時會有一段上下波動的波形,松開時也有一段猿棉。

軟件消抖常用的方法是延時磅叛,作用就是避開這一段“抖動”的波形,達到消抖的目的萨赁。

if(counter <= T) //按的時間不夠長
    begin
        counter = counter + 1'b1;
        up_key_press <= 0;
    end
else  //按下足夠久了弊琴,認(rèn)為是真的按下
    begin
         counter <= 0;
         up_key_press <= 1;
    end

硬件消抖這里也稍微拓展下。

RS觸發(fā)器實現(xiàn)

image

圖中兩個“與非”門構(gòu)成一個RS觸發(fā)器杖爽。當(dāng)按鍵未按下時敲董,輸出為0;當(dāng)鍵按下時紫皇,輸出為1。此時即使用按鍵的機械性能腋寨,使按鍵因彈性抖動而產(chǎn)生瞬時斷開(抖動跳開B)聪铺,只要按鍵不返回原始狀態(tài)A,雙穩(wěn)態(tài)電路的狀態(tài)不改變萄窜,輸出保持為0铃剔,不會產(chǎn)生抖動的波形。也就是說查刻,即使B點的電壓波形是抖動的键兜,但經(jīng)雙穩(wěn)態(tài)電路之后,其輸出為正規(guī)的矩形波赖阻。這一點通過分析RS觸發(fā)器的工作過程很容易得到驗證蝶押。

至于其他的硬件消抖電路,如:用電容構(gòu)成的積分電路實現(xiàn)火欧,采用D觸發(fā)器實現(xiàn)棋电,這里不再拓展,如有興趣苇侵,可自行查閱資料赶盔。

控制移動

這個功能的實現(xiàn),應(yīng)該說不難榆浓。知道要改變哪個參數(shù)能使它移動于未,改變它就可以實現(xiàn)了,在這個游戲系統(tǒng)中陡鹃,控制方塊上下移動是改變 move_y 這個參數(shù)烘浦,左右移動是改變move_x,不過在后面測試游戲時萍鲸,我覺得左右移動沒有必要加上去闷叉,就把它去了,具體的操作看代碼吧。

VGA模塊

實現(xiàn)這個游戲脊阴,我認(rèn)為最重要的知識就是VGA的顯示原理了握侧。數(shù)據(jù)怎么顯示在屏幕的?640和480指的又是什么嘿期?我們先看它的原理品擎。

VGA原理

VGA從掃描方式上分行掃描和場掃描兩種,掃描就是一個電子槍(CRT)备徐,啾啾啾的掃萄传,水平方向叫行掃描,垂直方向叫場掃描蜜猾,這個電子槍它又可發(fā)出三種顏色光秀菱,分別是R(紅色)西设,G(綠色),B(藍色)答朋,光的三原色都有贷揽,原則上三原色按照比例不同搭配,那你想要什么顏色就可以給你什么顏色梦碗,但實際上呢禽绪,VGA中紅,綠洪规,藍的輸入線分別是3,3,2根印屁;也就是說紅色有2^3=8種,同理斩例,綠色八種雄人,藍色四種,相互搭配念赶,便有8 X 8 X 4=256種搭配础钠,也就是VGA能顯示256種顏色。

掃描過程是怎樣的叉谜?

以行掃描為例:

image

從圖可以看出旗吁,電子槍從左往右掃射一行回頭再到下一行,直至最后完成一幀畫面停局,又重頭開始很钓。那什么時候掉頭,什么時候算是完成一幀畫面董栽,這就有個區(qū)域了码倦,區(qū)域怎么定,是由顯示模式?jīng)Q定的锭碳,看下圖袁稽。

image

我們可以看到有多種顯示模式,不同的顯示模式所需的時鐘頻率可是不一樣的工禾,如果細(xì)心查看运提,就會注意到蝗柔,行時序的c區(qū)和列時序的q區(qū)恰好是640和480這兩個熟悉的數(shù)字闻葵,其實這就是顯示時序段的范圍。

VGA中定義行時序和場時序都需要同步脈沖(Sync a)癣丧、顯示后沿(Back porch b)槽畔、顯示時序段(Display interval c)和顯示前沿(Front porch d)四部分。只有在顯示時序段胁编,也就是C區(qū)才可以信號顯示出來厢钧,其他區(qū)域鳞尔,你就算給VGA信號,你也看不到早直。

行時序

image

場時序

image

那么我們怎么知道寥假,電子槍(CRT)有沒有掃描到顯示時序段呢?方法是加入行同步計數(shù)器和列同步計數(shù)器用霞扬,反正時序是固定的糕韧,行同步計數(shù)器是掃一下計數(shù)器加一,列計數(shù)器是一行掃完計數(shù)器加一喻圃,兩者都掃到C區(qū)了萤彩,也就是計數(shù)器都達到一定數(shù)值(a區(qū)長度+B區(qū)長度),表明屏幕可以顯示信號斧拍,我就給信號雀扶,要黑色,rgb全給0肆汹,要白色愚墓,全給1,反正就是給信號昂勉,這和在坐標(biāo)軸上畫圖的感覺是一樣一樣的转绷。

控制模塊

控制模塊就像這個游戲系統(tǒng)的控制中心一般,制定了關(guān)于這個游戲的一切規(guī)則硼啤。

主要有那么幾個要點:

  1. 將鍵盤的長脈沖變?yōu)橐粋€個沖擊信號议经,不然以FPGA本身的頻率,按一下谴返,移動得太快煞肾,方塊就“上天” 了,障礙物移動也會快到你看不到。
  2. “畫”方塊和障礙物(擋板)嗓袱,并設(shè)定參數(shù)讓給它們可以移動籍救。
  3. 由于擋板的垂直方向出現(xiàn)的位置要有隨機性,所以需要產(chǎn)生隨機數(shù)渠抹。
  4. 設(shè)置游戲失敗的情況赖晶,方塊與擋板“撞上”這個時機的設(shè)置必須是程序完成映凳。

長脈沖變多個短脈沖

這不難想也不難實現(xiàn),就是計數(shù)器加到一定程度變?yōu)闃?biāo)志位變?yōu)?,然后計數(shù)器清零颜屠,標(biāo)志位也變?yōu)?桶略,一定時間內(nèi)要短脈沖多點跪呈,計數(shù)器的計數(shù)值就小點嚷量,反之,大一點。

////  板塊移動速度控制   ////
reg move;
reg [32:0]counter;
reg [30:0]T_move;
always@(posedge clk,negedge reset)
begin
    if(!reset)
    begin
        T_move = 30'd10_000_00;
        counter <= 0;
        move <=0;
    end
    else
    begin
        if(counter >= T_move)
        begin
            move = 1;
            if(T_move == 100_000)
                T_move <=T_move;
            else
                T_move = T_move-10;
            counter = 0;
        end
        else 
        begin
            move = 0;
            if(!stop)
                counter= counter + 1;
            else
                counter = 0;
        end
    end
end

“畫”方塊和擋板

關(guān)于如何“畫”荆几,前面舉過畫方塊的例子吓妆,就是把行列計數(shù)器當(dāng)做 x,y。x給個區(qū)域吨铸,y給個區(qū)域行拢,再給個顏色,就畫出來了诞吱。至于移動呢剂陡,移動就代表著位置是個變量,設(shè)定一個x,y都是變量的點狐胎,然后以這個點為中心畫出你要的方塊或者擋板鸭栖,改變這個點的x,y便是將它移動。

以擋板從右向左移動為例:

image

產(chǎn)生隨機數(shù)

產(chǎn)生偽隨機數(shù)的方法最常見的是利用一種線性反饋移位寄存器(LFSR),它是由n個D觸發(fā)器和若干個異或門組成的握巢,如下圖:

[圖片上傳失敗...(image-fadcea-1636785474160)]

實際上這個有規(guī)律可循的晕鹊,只不過D觸發(fā)器一多,顯得很亂暴浦,很像隨機產(chǎn)生的樣子溅话,但確實不是真正意義上的隨機數(shù),是個偽隨機數(shù)歌焦,但在這里使用足夠了的飞几。

但這種方法也有bug,就是高位它不容易變化的時候,擋板垂直方向就不夠分散独撇,舉個例子屑墨,以8個D觸發(fā)器組成的為例,數(shù)字范圍從0~1111_1111,如果高位變化不大纷铣,如從1110_0000變成1110_0101,高位不怎么變化的話卵史,整個數(shù)字大小實際上就是改變一點點,圖像表現(xiàn)為前后兩個擋板垂直位置上相差幾個像素點搜立,這就顯得過于集中以躯,而且這種辦法無法生成 0 這個數(shù)字。

為了將擋板"離散一點"啄踊,我就將豎直方向的長減去擋板的長度后得到的空隙分段化忧设,分為8段,這樣擋板之間的距離要么相等颠通,不然都會有一段距離址晕,顯得離散些。怎么實現(xiàn)呢蒜哀?

每個D觸發(fā)器都存有一個數(shù)字斩箫,我從中隨機抽取三個數(shù)字,做一個case語句的選擇撵儿,8段8種情況選擇乘客。這樣隨機性增加,擋板也更離散淀歇。

///////          隨機數(shù)     //////////
 reg [7:0] rand_num;
parameter seed = 8'b1111_1111;
always@(posedge clk or negedge reset)
begin
   if(!reset)
       rand_num  <= seed;
   else
       begin
           rand_num[0] <= rand_num[1] ;
           rand_num[1] <= rand_num[2] + rand_num[7];
           rand_num[2] <= rand_num[3] + rand_num[7];
           rand_num[3] <= rand_num[4] ;
           rand_num[4] <= rand_num[5] + rand_num[7];
           rand_num[5] <= rand_num[6] + rand_num[7];
           rand_num[6] <= rand_num[7] ;
           rand_num[7] <= rand_num[0] + rand_num[7];     
       end
end
wire [2:0]choose;
reg [8:0]type;
assign choose = {rand_num[3],rand_num[6],rand_num[2]};
always@(posedge clk )
begin
    case(choose) 
    0:type = 0;
    1:type = 40;
    2:type = 80;
    3:type = 120;
    4:type = 160;
    5:type = 200;
    6:type = 240;
    7:type = 280;
    endcase
end
////////////////////////////////////////////////////////

游戲失敗設(shè)置

游戲失敗是撞上了易核,那 撞上 在數(shù)學(xué)上表示是什么呢?

答案是方塊和擋板的坐標(biāo)有交叉浪默。

方塊和擋板之間都有坐標(biāo)的區(qū)域牡直,只要找到它們會交叉的情況,就說明這個時候是撞上了纳决。原理就是如此碰逸,具體的可以自己動筆算下。

友情提醒:加減時注意盡量不要出現(xiàn)負(fù)數(shù)的情況阔加,因為 數(shù)字用補碼表示的原因饵史,在FPGA中,直接比較 胜榔,-1=ffff_ffff 可是大于0的胳喷。

wire die1,die2,die3,die4;
//游戲失敗定義,方塊與擋板"碰撞"
//失敗情況討論夭织,共設(shè)置四塊擋板吭露,四種情況
assign die1=((rand<move_y + border)&&(move_y < rand+long)&&(push < move_x+border) && (move_x < push + ban ));
assign die2=((rand1<move_y + border)&&(move_y < rand1+long)&&(push1 < move_x+border) && (move_x < push1 + ban ));
assign die3=((rand2<move_y + border)&&(move_y < rand2+long)&&(push2 < move_x+border) && (move_x < push2 + ban ));
assign die4=((rand3<move_y + border)&&(move_y < rand3+long)&&(push3 < move_x+border) && (move_x < push3 + ban ));

wire false;
assign false = die1||die2||die3||die4;

代碼展示

鍵盤模塊

module key(clk,reset,up,up_key_press,down_key_press);
input clk;
input reset;
input up;
output reg up_key_press;
output reg down_key_press;

parameter T = 30'd10_000_00;  //控制方塊移動速度

//////////   up 按鍵   /////////////
reg [30:0] counter;
reg [30:0] counter2;
always@(posedge clk,negedge reset )
begin
     if(!reset)
        begin
            counter <= 0;
            counter2 <= 0;
            up_key_press <= 0;
            down_key_press <= 0;
         end
     else
        begin
            if(up)
                begin
                    if(counter <= T)
                        begin
                            counter = counter + 1'b1;
                            up_key_press <= 0;
                        end
                    else
                        begin
                            counter <= 0;
                            up_key_press <= 1;
                        end
                end
             else  //下降按鈕
                begin
                    if(counter2 <= T)
                        begin
                            counter2 = counter2 +  1'b1;
                            down_key_press <= 0;
                        end
                    else
                        begin
                            counter2 <= 0;
                            down_key_press <= 1;
                        end
                end
        end
end

endmodule

VGA模塊

module vga( clk,reset,hsync, vsync,hc,vc,dat_act);
            input clk; //系統(tǒng)輸入時鐘 100MHz
            input reset;

            output hsync; //VGA 行同步信號
            output vsync; //VGA 場同步信號
            output dat_act;
            output [9:0]hc ,vc; //轉(zhuǎn)成640*480的模式
            
            reg [9:0] hcount; //VGA 行掃描計數(shù)器
            reg [9:0] vcount; //VGA 場掃描計數(shù)器

            reg flag;
            wire hcount_ov;
            wire vcount_ov;

            wire hsync;
            wire vsync;

            reg vga_clk=0;
            reg cnt_clk=0; //分頻計數(shù)

            //VGA 行、場掃描時序參數(shù)表
            parameter hsync_end = 10'd95,
            hdat_begin = 10'd143,
            hdat_end = 10'd783,
            hpixel_end = 10'd799,

            vsync_end = 10'd1,
            vdat_begin = 10'd34,
            vdat_end = 10'd514,
            vline_end = 10'd524;


        //分頻
            always @(posedge clk)
            begin
                if(cnt_clk == 1)
                begin
                    vga_clk <= ~vga_clk;
                    cnt_clk <= 0;
                 end
                else
                    cnt_clk <= cnt_clk +1;
            end

  //************************VGA 驅(qū)動部分*******************************//行掃描

            always @(posedge vga_clk)
            begin
                if (hcount_ov)
                    hcount <= 10'd0;
                 else
                     hcount <= hcount + 10'd1;
            end
            assign hcount_ov = (hcount == hpixel_end);

            //場掃描
            always @(posedge vga_clk)
            begin
                if (hcount_ov)
                begin
                    if (vcount_ov)
                        vcount <= 10'd0;
                    else
                        vcount <= vcount + 10'd1;
                end
            end
            assign vcount_ov = (vcount == vline_end);

            //數(shù)據(jù)尊惰、同步信號輸
            assign dat_act = ((hcount >= hdat_begin) && (hcount < hdat_end))&& ((vcount >= vdat_begin) && (vcount < vdat_end));
            assign hsync = (hcount > hsync_end);
            assign vsync = (vcount > vsync_end);
           
            //計數(shù)器轉(zhuǎn)成640 x 480的樣式讲竿,方便開發(fā) 
            assign hc = hcount - hdat_begin;
            assign vc = vcount - vdat_begin;
            
endmodule

控制模塊

module control( clk,reset, disp_RGB,hc,vc,dat_act,up_key_press,down_key_press );
            input clk; //系統(tǒng)輸入時鐘 100MHz
            input reset;
            input dat_act;
            input [9:0]hc,vc;
            input up_key_press;
            input down_key_press;

            output [2:0]disp_RGB; //VGA 數(shù)據(jù)輸出
            
            reg [2:0]data;
            reg vga_clk=0;
            reg cnt_clk=0; //分頻計數(shù)


        //分頻
            always @(posedge clk)
            begin
                if(cnt_clk == 1)
                begin
                    vga_clk <= ~vga_clk;
                    cnt_clk <= 0;
                 end
                else
                    cnt_clk <= cnt_clk +1;
            end
            //定義正方形小塊的邊長
            parameter border = 40;
            //定義擋板的寬度
            parameter ban = 20;
            //定義擋板的長度
            parameter long = 200;
            //定義擋板的間隔
            parameter magin = 160;
            
            //VGA掃描,畫出擋板和方塊弄屡,并設(shè)置擋板移動的移動變量push
            reg [10:0] push,push1,push2,push3;
            reg stop;//用于停止游戲
            
            //小方塊移動數(shù)據(jù)存儲器
            parameter move_x = 50; //方塊的初始位置
            reg [9:0]move_y;
            
///////          隨機數(shù)     //////////
 reg [7:0] rand_num;
parameter seed = 8'b1111_1111;
always@(posedge clk or negedge reset)
begin
   if(!reset)
       rand_num  <= seed;
   else
       begin
           rand_num[0] <= rand_num[1] ;
           rand_num[1] <= rand_num[2] + rand_num[7];
           rand_num[2] <= rand_num[3] + rand_num[7];
           rand_num[3] <= rand_num[4] ;
           rand_num[4] <= rand_num[5] + rand_num[7];
           rand_num[5] <= rand_num[6] + rand_num[7];
           rand_num[6] <= rand_num[7] ;
           rand_num[7] <= rand_num[0] + rand_num[7];     
       end
end
wire [2:0]choose;
reg [8:0]type;
assign choose = {rand_num[3],rand_num[6],rand_num[2]};
always@(posedge clk )
begin
    case(choose) 
    0:type = 0;
    1:type = 40;
    2:type = 80;
    3:type = 120;
    4:type = 160;
    5:type = 200;
    6:type = 240;
    7:type = 280;
    default: type = 280;
    endcase
end
////////////////////////////////////////////////////////


////  板塊移動速度控制   ////
reg move;
reg [32:0]counter;
reg [30:0]T_move;
always@(posedge clk,negedge reset)
begin
    if(!reset)
    begin
        T_move = 30'd10_000_00;
        counter <= 0;
        move <=0;
    end
    else
    begin
        if(counter >= T_move)
        begin
            move = 1;
            if(T_move == 100_000)
                T_move <=T_move;
            else
                T_move = T_move-10;
            counter = 0;
        end
        else 
        begin
            move = 0;
            if(!stop)
                counter= counter + 1;
            else
                counter = 0;
        end
    end
end
reg [8:0]rand,rand1,rand2,rand3;
always@(posedge clk or negedge reset)
begin
    if (!reset)
        begin
           push<=640;  //初始位置設(shè)定
           push1 <= 640+ magin;
           push2 <= 640 + magin + magin;
           push3 <= 640 + magin + magin + magin;
        end
else if (move)
    begin
        if(push == 0)
            begin
                 push <= 640;
                 rand <=type; //第一塊板子的位置設(shè)定
            end
        else
            begin                        
                push <= push-1'b1;                                     
            end
         if(push1 == 0)
                begin
                     push1 <= 640;
                     rand1 <=type; //第二塊板子的位置設(shè)定
                end
            else
                begin                        
                    push1 <= push1-1'b1;                                     
                end
        if(push2 == 0)
                    begin
                         push2 <= 640;
                         rand2 <=type; //第三塊板子的位置設(shè)定
                    end
                else
                    begin                        
                        push2<= push2-1'b1;                                     
                    end
        if(push3 == 0)
                        begin
                             push3 <= 640;
                             rand3 <=type; 
                          //第四塊板子的位置設(shè)定
                        end
                    else
                        begin                        
                            push3 <= push3-1'b1;                                     
                        end        
    end
    else
    begin
        push <= push;
        push1 <= push1;
        push2 <= push2;
        push3 <= push3;
    end
end


wire die1,die2,die3,die4;
//游戲失敗定義戴卜,方塊與擋板"碰撞"
//失敗情況討論,共設(shè)置四塊擋板琢岩,四種情況
assign die1=((rand<move_y + border)&&(move_y < rand+long)&&(push < move_x+border) && (move_x < push + ban ));
assign die2=((rand1<move_y + border)&&(move_y < rand1+long)&&(push1 < move_x+border) && (move_x < push1 + ban ));
assign die3=((rand2<move_y + border)&&(move_y < rand2+long)&&(push2 < move_x+border) && (move_x < push2 + ban ));
assign die4=((rand3<move_y + border)&&(move_y < rand3+long)&&(push3 < move_x+border) && (move_x < push3 + ban ));

wire false;
assign false = die1||die2||die3||die4;

//描述運動投剥,“畫圖”
always@(posedge vga_clk,negedge reset)
begin
    if(!reset)
        begin 
            data <= 0;
            stop <= 0;
        end
    else 
        begin 
           if (hc>move_x &&(hc<(move_x+border)&&(vc>move_y)&&(vc<move_y+border))) //小方塊
               begin
                   if(!false)
                        begin
                            data <= 3'h3; //黃色
                            stop <= 0;
                        end
                   else
                        begin
                            data <= 3'h1; //紅色
                            stop <=1;
                        end
                end   
          else
                if ((hc>push) && (hc<=push+ban) && (vc>=rand) && (vc<=rand+long))
                     begin
                         data <= 3'h2;  //第一根橫條
                     end      
                else  if ((hc>push1) && (hc<=push1+ban) && (vc>=rand1) && (vc<=rand1+long))
                        begin
                           data <= 3'h2;  //第二根橫條
                        end 
                 else  if ((hc>push2) && (hc<=push2+ban) && (vc>=rand2) && (vc<=rand2+long))
                             begin
                                data <= 3'h2;  //第三根橫條
                             end 
                          else  if ((hc>push3) && (hc<=push3+ban) && (vc>=rand3) && (vc<=rand3+long))
                                  begin
                                   data <= 3'h2;  //第四根橫條
                                  end                                                       else
                                     data <= 0;
        end
end


///////       方塊移動控制       ////////////
    always@(posedge clk or negedge reset)
    begin
        if (!reset)
            begin
               move_y <= 240;
            end
    else if (up_key_press)
        begin
            if(move_y == 0)
                begin
                     move_y <= move_y;
                end
            else
                begin                        
                    move_y <= move_y-1'b1;                                          
                end
        end
      else if (down_key_press)
            begin
                if(move_y>440)
                begin
                     move_y <= move_y;
                end
            else
                 begin    
                    move_y <= move_y+1'b1;    
                 end
            end 
end
// 信號輸出
assign disp_RGB = (dat_act) ? data : 3'h00;
endmodule

TOP模塊

module top(clk,reset,up,hsync,vsync,disp_RGB);
input clk;
input reset;
input up;

output hsync; //VGA 行同步信號
output vsync; //VGA 場同步信號
output [2:0]disp_RGB; //VGA 數(shù)據(jù)輸出

wire dat_act;
wire up_key_press;
wire down_key_press;
wire [9:0]hc,vc;


key U1(clk,reset,up,up_key_press,down_key_press);
control U2( clk,reset, disp_RGB,hc,vc,dat_act,up_key_press,down_key_press );
vga  U3( clk,reset,hsync, vsync,hc,vc,dat_act);
endmodule

寫在后面的話

關(guān)于這個小游戲的講解就到這里,有任何疑問可以在評論處指出或者聯(lián)系我担孔,我會及時更新江锨,文章若有錯誤,懇請讀者在評論區(qū)指出斧正糕篇,我會修改啄育。

歡迎大家在評論區(qū)與我交流,學(xué)習(xí)拌消。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末挑豌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌氓英,老刑警劉巖侯勉,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異铝阐,居然都是意外死亡址貌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門徘键,熙熙樓的掌柜王于貴愁眉苦臉地迎上來练对,“玉大人,你說我怎么就攤上這事吹害∶荆” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵它呀,是天一觀的道長赂摆。 經(jīng)常有香客問我,道長钟些,這世上最難降的妖魔是什么烟号? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮政恍,結(jié)果婚禮上汪拥,老公的妹妹穿的比我還像新娘。我一直安慰自己篙耗,他們只是感情好迫筑,可當(dāng)我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宗弯,像睡著了一般脯燃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蒙保,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天辕棚,我揣著相機與錄音,去河邊找鬼邓厕。 笑死逝嚎,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的详恼。 我是一名探鬼主播补君,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼昧互!你這毒婦竟也來了挽铁?” 一聲冷哼從身側(cè)響起伟桅,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叽掘,沒想到半個月后楣铁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡够掠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年民褂,在試婚紗的時候發(fā)現(xiàn)自己被綠了茄菊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疯潭。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖面殖,靈堂內(nèi)的尸體忽然破棺而出竖哩,到底是詐尸還是另有隱情,我是刑警寧澤脊僚,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布相叁,位于F島的核電站,受9級特大地震影響辽幌,放射性物質(zhì)發(fā)生泄漏增淹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一乌企、第九天 我趴在偏房一處隱蔽的房頂上張望虑润。 院中可真熱鬧,春花似錦加酵、人聲如沸拳喻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冗澈。三九已至,卻和暖如春陋葡,著一層夾襖步出監(jiān)牢的瞬間亚亲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工腐缤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留朵栖,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓柴梆,卻偏偏與公主長得像陨溅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绍在,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 上午在教室门扇,學(xué)習(xí)專業(yè)課+六級 下午回寢室雹有,完成演講課的內(nèi)容,并補打卡兩天臼寄;以及影樓的三節(jié)課+一次作業(yè) 晚上學(xué)習(xí)數(shù)學(xué)...
    尼古拉斯邶閱讀 152評論 0 0
  • Knowing that it is difficult for you to adapt to your new...
    如一少年閱讀 117評論 0 0
  • 有哪些是你發(fā)現(xiàn)是常態(tài)霸奕,卻又受它影響的行為模式? 1.細(xì)琢往事 昨晚3點多整完孩子尿床的事吉拳,意識也開始清醒了质帅,在輾轉(zhuǎn)...
    艾媽幫閱讀 115評論 0 1
  • 第一,多讀留攒,每一個文字背后都是一個有趣的故事煤惩,開卷有益嘛! 第二炼邀,多寫魄揉,寫字能夠釋放壓力,信筆涂鴉拭宁,動筆有益洛退。 第...
    時光清閑閱讀 208評論 0 0
  • 今天是什么日子 起床:8:30 就寢:12:40 天氣:陰 心情:一般 紀(jì)念日:開啟100天時間清單記錄 叫我起床...
    ac19a8199059閱讀 340評論 0 0