FPGAベースのシンプルなゲームを作成する

1






こんにちはHabr。 私は最近FPGAを学び始めました。 PS / 2およびVGAインターフェイスの研究を目的とした私のプロジェクトの1つは、一人でピンポンをプレイすることでした。 実装の1つはDE0-CVボードで動作します。これは、コンテストの一環として素晴らしいシリコンロシアプロジェクトから提供されました( http://www.silicon-russia.com/2015/12/11/board-giveaway-for-mipsfpga/ ) 。



ゲームの本質:キーボードから制御されるスライダーがあり、ボールを打って画面上を移動します。 ディスプレイメディアとしてVGAディスプレイが選択され、シンプルなPS / 2インターフェイスを備えたキーボードが選択されました。 ゲーム自体のスコアは、7セグメントインジケーターに表示されます。



デバッグボード



2






DE0-CVは、Alterが配布する公式デバッグボードで、価格は150ドル、アカデミック向けは99ドルです。 ボード自体には次のものがあります。



-6つの7セグメントインジケータ、10個のLED、10個のスイッチ、4個のボタン。

-VGAコネクタ、PS / 2コネクタ、マイクロSDカード用スロット。



-64 MBのSDRAMメモリ。



-それぞれ35ピンの2つのGPIOコネクタ。



作業ロジック



3






プログラムは4つのメインブロックを区別できます。 それぞれが特定の機能を実行します。



プログラム全体の中心はPLLです。 適切な構成のおかげで、VGAで作業し、他のブロックをクロックすることができます。



4






PS / 2キーボードコントローラー



ゲームのラケットを制御するために、PS / 2インターフェイスを備えたキーボードを使用します。 ブロックの実装を検討する前に、PS / 2プロトコルについて少し見てみましょう。



5








PS / 2プロトコルでのデータ交換に使用される出力は、データとクロックの出力です。 送信ビットは、1つの開始ビット、8つのデータビット、パリティビット、および停止ビットで構成されます。 クロック出力は、ご想像のとおり、クロッキングです。



ビットは、クロックの立ち上がりエッジと立ち上がりエッジでデバイス側に設定され、読み取り値は信号の立ち下がりエッジでデバイス側に設定されます。 デバイスが何も送信していない場合、クロックとデータは電源に供給されます。 次に、データバスとクロックバスがゼロになります。これは、メッセージの送信が開始されたことを示しています。 8ビットを読み取った後、パリティビットとストップビットがあり、常に1です。



最初のハンドラーでは、ボタンが押されているかどうかを理解するためにクロックをカウントします。 PS2_CLK_inが52500000サイクル以内に設定されている場合、ボタンは押されません。 また、ここで押されたキーのコードを確認します。

-押されたキーのコードが上矢印キーのコードと一致する場合、出力は1になります。

-下矢印キーを押すと、下出力が1になります。

always @(negedge clock) begin if(PS2_CLK_in == 1) count_clk <= count_clk + 1; else count_clk <= 0; if(count_clk>=52500000) begin led_out <= 0; end else led_out <= bit; if(led_out == 8'b01110010) begin down <= 1; up <= 0; end else if(led_out == 8'b01110101) begin up <= 1; down <= 0; end else begin down <= 0; up <= 0; end end
      
      





入力PS2_CLK_inでHighからLowへの遷移が検出された場合、ステータスは入力PS2_DAT_inから読み取られます。

 always @(negedge PS2_CLK_in) begin if(s == 0) begin if(count<=7) begin bit <= bit|(PS2_DAT_in<<count); end if(count == 9) begin s <= 1; end else begin count <= count + 1; end end if(s == 1) if(PS2_DAT_in == 0) begin s <= 0; count <= 0; bit <= 0; end end endmodule
      
      





ModelSimでテストするためのコードを次に示します。

 initial begin #0 clock_r=1; #275 clock_r = 1; //s repeat( 22 ) begin #25 clock_r=~clock_r; end #100 clock_r = 1; repeat( 22 ) begin #25 clock_r=~clock_r; end #300 clock_r = 1; repeat( 22 ) begin #25 clock_r=~clock_r; end #50 clock_r = 1; repeat( 22 ) begin #25 clock_r=~clock_r; end end initial begin #250 PS2_CLK_r = 1; //s #50 PS2_CLK_r = 0; //start #50 PS2_CLK_r = 0; //0 #50 PS2_CLK_r = 1; //1 #50 PS2_CLK_r = 1; //2 #50 PS2_CLK_r = 0; //3 #50 PS2_CLK_r = 1; //4 #50 PS2_CLK_r = 0; //5 #50 PS2_CLK_r = 1; //6 #50 PS2_CLK_r = 1; //7 #50 PS2_CLK_r = 1; //parity bit #50 PS2_CLK_r = 0; //stop #50 PS2_CLK_r = 1; //s #50 PS2_CLK_r = 1; //s #50 PS2_CLK_r = 0; //start #50 PS2_CLK_r = 1; //0 #50 PS2_CLK_r = 1; //1 #50 PS2_CLK_r = 0; //2 #50 PS2_CLK_r = 0; //3 #50 PS2_CLK_r = 1; //4 #50 PS2_CLK_r = 0; //5 #50 PS2_CLK_r = 1; //6 #50 PS2_CLK_r = 1; //7 #50 PS2_CLK_r = 1; //parity bit #50 PS2_CLK_r = 0; //stop #50 PS2_CLK_r = 1; //s #250 PS2_CLK_r = 1; //s #50 PS2_CLK_r = 0; //start #50 PS2_CLK_r = 0; //0 #50 PS2_CLK_r = 1; //1 #50 PS2_CLK_r = 1; //2 #50 PS2_CLK_r = 1; //3 #50 PS2_CLK_r = 1; //4 #50 PS2_CLK_r = 1; //5 #50 PS2_CLK_r = 1; //6 #50 PS2_CLK_r = 1; //7 #50 PS2_CLK_r = 1; //parity bit #50 PS2_CLK_r = 0; //stop #50 PS2_CLK_r = 1; //s #50 PS2_CLK_r = 0; //start #50 PS2_CLK_r = 0; //0 #50 PS2_CLK_r = 1; //1 #50 PS2_CLK_r = 1; //2 #50 PS2_CLK_r = 0; //3 #50 PS2_CLK_r = 1; //4 #50 PS2_CLK_r = 0; //5 #50 PS2_CLK_r = 1; //6 #50 PS2_CLK_r = 1; //7 #50 PS2_CLK_r = 1; //parity bit #50 PS2_CLK_r = 0; //stop #50 PS2_CLK_r = 1; //s #50 PS2_CLK_r = 1; //s #50 PS2_CLK_r = 1; //s end assign clock = clock_r; assign PS2_DAT_in = PS2_CLK_r;
      
      





ブロック動作図:



6


VGAブロックの動作。



DE0ボードにはVGA出力があり、RGB出力用のDACとして、単純な抵抗回路が使用されます。



VGAの使用を開始するには、VESA仕様(http://tinyvga.com/vga-timing)を調べて、目的の動作モードを選択する必要があります。 必要な頻度とタイミングを表示します。 ビデオモード1440x900 60Hzを選択します。 必要なクロック周波数は106.5 MHzです。



ボードには50 MHzの水晶があります。 特別なPLLブロックを使用して、50 MHzを必要な106.5に変換できます。 これを行うには、目的のブロックを作業領域に引き出して構成する必要があります



7


ドキュメントから必要なタイミングを取ります:



8


 parameter h_front_porch = 80; parameter h_sync = 152; parameter h_back_porch = 232; parameter h_active_pixels = 1440; parameter v_front_porch = 3; parameter v_sync = 6; parameter v_back_porch = 25; parameter v_active_scanilines = 900;
      
      





pixel_clock入力で受信した各ポジティブエッジに対して、pixel_countカウンターを1増やし、その値に応じて、目的の論理レベルを水平同期出力hsyncに設定します。

 wire w_hsync = (pixel_count < h_sync); always @(posedge pixel_clock) begin hsync <= (pixel_count < h_sync); hvisible <= (pixel_count >= (h_sync+h_back_porch)) && (pixel_count < (h_sync+h_back_porch+h_active_pixels)); if(pixel_count < (h_sync+h_back_porch+h_active_pixels+h_front_porch) ) begin pixel_count <= pixel_count + 1'b1; char_count <= pixel_count; end else begin pixel_count <= 0; end end
      
      





pixel_countカウンターが行の終わりに達すると、line_count行カウンターが増加し、以前に設定されたパラメーターに応じて、vsync垂直同期出力に必要な値が設定されます。

 wire w_hsync_buf = w_hsync&~hsync; always @(posedge pixel_clock) begin if(w_hsync_buf)begin vsync <= (line_count < v_sync); vvisible <= (line_count >= (v_sync+v_back_porch)) && (line_count < (v_sync+v_back_porch+v_active_scanilines)); if(line_count < (v_sync+v_back_porch+v_active_scanilines+v_front_porch) )begin line_count <= line_count + 1'b1; line_count_out <= line_count; end else begin line_state <= 0; line_count <= 0; end end end
      
      





pixel_countとline_countが画面の表示部分に属する範囲に入ると、visibleが高レベルに設定され、ゲームブロックがプレイフィールドの描画を開始できるようになります。

 always @* begin visible <= hvisible & vvisible; end
      
      





ジョブブロックゲーム。



pixel_state信号の論理ユニットへの遷移は、vgaブロックからプレイフィールドを描画する許可を取得することを意味します。 入力信号char_countおよびline_countは、現在画面に描画されているポイントの座標について通知します。 ボールとラケットの座標に基づいて、それらに対応するゾーンを必要な色で塗りつぶします。

 always @(pixel_state) begin if((char_count>=start_horz) && (char_count<=start_horz+50))begin if((line_count>=i) && (line_count<=i+100)) begin VGA_BLUE<=6'b111110; end else VGA_BLUE<=6'b000000; end else VGA_BLUE<=6'b000000; if((ball_x-char_count)*(ball_x-char_count)+(ball_y-line_count)*(ball_y-line_count)<400) VGA_RED<=5'b11110; else VGA_RED<=5'b00000; end
      
      





ボールとラケットの座標は、クロック信号clkの上昇エッジで再計算されます。 また、ボールが壁に衝突すると、その動きの方向が変わります。

 always @(posedge clk) begin if(key_2==0) begin if(i<vert_sync+vert_back_porch+vert_addr_time) i=i+1; else i=0; end if(key_0==0) begin if(i>vert_sync+vert_back_porch) i=i-1; else i=vert_sync+vert_back_porch+vert_addr_time; end if(flag == 2'b00) begin ball_x=ball_x-1; ball_y=ball_y-1; end if(flag == 2'b01) begin ball_x=ball_x+1; ball_y=ball_y+1; end if(flag == 2'b10) begin ball_x=ball_x-1; ball_y=ball_y+1; end if(flag == 2'b11) begin ball_x=ball_x+1; ball_y=ball_y-1; end if(ball_y<=vert_sync+vert_back_porch) if(flag==2'b00) flag=2'b10; else flag=2'b01; if(ball_x<=horz_sync+horz_back_porch) if(flag==2'b10) flag = 2'b01; else flag = 2'b11; if(ball_y>=vert_sync+vert_back_porch+vert_addr_time) if(flag==2'b01) flag=2'b11; else flag=2'b00; if(ball_x>=start_horz && ball_y>=i && ball_y<=i+100) if(flag==2'b11) flag=2'b00; else flag=2'b10; if(ball_x>=horz_sync+horz_back_porch+horz_addr_time) begin if(goal_2==9) begin goal_2<=0; goal<=goal+1; end else goal_2<=goal_2+1; if(flag==2'b11) flag<=2'b00; else flag<=2'b10; end end
      
      





次の場合:

-競技場の右端に近づいたときにボールがラケットと出会わない場合、ゴールの変化が発生するため、7セグメントインジケーターに表示されるスコアが1つ増加します。

-ゴールオーバーフロー-発生:goal_2が変化し、小数点以下1桁増加します。

 always @(clk) begin case(goal) 0: HEX_1 <= 7'b1000000; 1: HEX_1 <= 7'b1111001; 2: HEX_1 <= 7'b0100100; 3: HEX_1 <= 7'b0110000; 4: HEX_1 <= 7'b0011001; 5: HEX_1 <= 7'b0010010; 6: HEX_1 <= 7'b0000010; 7: HEX_1<= 7'b1111000; 8: HEX_1 <= 7'b0000000; 9: HEX_1 <= 7'b0010000; default: HEX_1 <= 7'b1111111; endcase end always @(clk) begin case(goal_2) 0: HEX_2 <= 7'b1000000; 1: HEX_2 <= 7'b1111001; 2: HEX_2 <= 7'b0100100; 3: HEX_2 <= 7'b0110000; 4: HEX_2 <= 7'b0011001; 5: HEX_2 <= 7'b0010010; 6: HEX_2 <= 7'b0000010; 7: HEX_2 <= 7'b1111000; 8: HEX_2 <= 7'b0000000; 9: HEX_2<= 7'b0010000; default: HEX_2 <= 7'b1111111; endcase end
      
      





おわりに



結果のプロジェクトを合成し、FPGAで使用されるリソースの統計を取得します。



9


このプロジェクトを実装するとき、FPGAを使用すると、VGAなどの複雑なインターフェイスを実装するのが非常に簡単で、MKを使用するのが困難な非常に高いタイミング要件があることがわかりました。 https://github.com/MIPSfpga/pre-mipsfpga/tree/master/pinpong



PS:私は最近FPGAの世界に参入しました。コードを焼き付けた経験豊富な人々に本当に謝罪します。 ご理解、ご容赦、アドバイスをお願いします。



All Articles