Posts Tagged with "FPGA"

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.

敵弾の貫通度の修正 (2)

posted by sakurai on June 16, 2021 #422

敵弾種類

敵弾名はインベーダゲームソースの研究から引用しています。図422.1の左から、Squiggly (抵抗器のようなジグザグ)、Rolling、Plunger (パイプつまり直し)と名前がついています。

図%%.1
図422.1 敵弾の種類

図422.2において、敵弾の貫通度は左からそれぞれ3, 3, 7としています。

BSVコードの修正

元々敵弾をランダム(風)に出現させるため、カウンタの値により敵弾タイプを決めていますが、同じように貫通度を決めるテーブルです。

UInt#(3) invBullet_pdval[8] =   { 7,  3, 3,  7, 3,  3,  7, 3}; // pd = penetrating depth

次は貫通度を記憶するためのレジスタであり、敵弾数だけ持つ必要があります。

Reg#(UInt#(3)) invBullet_pd[`INV_BULLET_MAX];

このレジスタを各敵弾にひとつ持たせるために敵弾の同時最大数だけインスタンシエートします。

for (int ii = 0; ii < `INV_BULLET_MAX; ii = ii + 1) begin
 :
   invBullet_pd[ii] <- mkRegU;
 :
end

敵弾発生部において、タイプを決定するのと同じロジックにより、上記の表を引きます。

            invBullet_type[idx] <= invBullet_rand[truncate(counter) & 3'h7];
            invBullet_pd[idx] <= invBullet_pdval[truncate(counter) & 3'h7];

敵弾衝突の一部です。この2つのseq - endseqブロックは元は一つでしたが、base(バリケード)の時だけこの貫通度を使用するように分離します。他の場合は最深(貫通度=7)固定とします。

                endseq else if (fbase) seq
                    eraseInvBullet(invBullet_x[idx], invBullet_y[idx]);     // 現敵弾消去
                    explodeInvBullet(invBullet_x[idx], invBullet_y[idx], invBullet_pd[idx]);   // 次敵弾爆発
                    invBullet_expTimer[idx] <= 1;           // 敵弾爆発タイマスタート
                    voff <= 5; // break 'for'
                 endseq else if (fobj || invBullet_y[idx] >= 225) seq
                    eraseInvBullet(invBullet_x[idx], invBullet_y[idx]);     // 現敵弾消去
                    invBullet_pd[idx] <= 7; // 最深
                    explodeInvBullet(invBullet_x[idx], invBullet_y[idx], 7);   // 次敵弾爆発
                    invBullet_expTimer[idx] <= 1;           // 敵弾爆発タイマスタート
                    voff <= 5; // break 'for'

コール先の爆発ルーチンです。爆発は、爆発マークを表示することで開始します。

// 敵弾爆発
function Stmt explodeInvBullet(
              UInt#(8) destx,
              UInt#(8) desty,
              UInt#(3) pd);
   return (seq
      orArea(43, 16, destx - 3, desty + extend(pd), 8, 8);
   endseq);
endfunction

爆発マーク消去コール部分です。爆発開始から一定時間後に、保存してある貫通度を使用して爆発マークを消去します。

           expEraseInvBullet(invBullet_x[idx], invBullet_y[idx], invBullet_pd[idx]);        // 敵弾爆発マーク消去

コール先の爆発マーク消去ルーチンです。

// 敵弾爆発マーク消去
function Stmt expEraseInvBullet(
              UInt#(8) destx,
              UInt#(8) desty,
              UInt#(3) pd);
   return (seq
      eraseAreaSP(43, 16, destx - 3, desty + extend(pd), 8, 8);
   endseq);
endfunction

左矢前のブログ 次のブログ右矢

敵弾の貫通度の修正

posted by sakurai on June 15, 2021 #420

Youtubeの動画の分析

この動画を観察すると、バリケード(シールド)への貫通度が、敵弾の種類によって異なるように思われます。

下図は左端のインベーダからT字型(Plunger)の敵弾が発射されたところです。

図%%.1
図420.1 敵弾1

敵弾が左端のバリケードに着弾しました。深く潜って(貫通度=7)爆発しています。

図%%.2
図420.2 敵弾1爆発

同じく左端のインベーダからジグザグ型(Squiggly)の敵弾が発射されたところです。

図%%.3
図420.3 敵弾2

敵弾が左端のバリケードに着弾しました。浅く潜って(貫通度=3)爆発しています。明らかに敵弾の種類により貫通度が異なっています。

図%%.4
図420.4 敵弾2爆発

動画を観察した限りでは、T字型の敵弾が7、他の敵弾は貫通度=3で爆発するようでした。一方、最下端では7で爆発するようなので、バリケードで爆発する際にのみ、貫通度(penetration distance)を設定することにします。


左矢前のブログ 次のブログ右矢

オープニングアニメーション (3)

posted by sakurai on June 11, 2021 #418

呼び出し側

前々稿に個別ファンクションを掲載したので、それをオープニングアニメーションとしてまとめるファンクションです。オープニングアニメーション時を示すようにfoaというフラグをTrueにしています。これがTrueの場合は、ウエイトルーチンにおいてボタンが押された場合にウエイトを中止する仕様となっています。

function Stmt openingAnimation;
   return (seq
      // Opening Animation
      foa <= True;
      eraseArea( 0, 41, 255, 199); // erase screen
      eraseArea(25,242, 5, 7); // erase zanki
      stringS1; // PLAY ...
      if (sbutton) break;
      wait_timer(`TICK_WAIT64);
      if (sbutton) break;
      stringS2; // *SCORE ...
      if (sbutton) break;
      wait_timer(`TICK_WAIT32);
      if (sbutton) break;
      stringS3; // =? MYSTERY ...
      if (sbutton) break;
      wait_timer(`TICK_WAIT64);
      if (sbutton) break;
      replaceY; // ^ -> Y
      if (sbutton) break;
      wait_timer(`TICK_WAIT64);
      if (sbutton) break;
      foa <= False;
   endseq);
endfunction 

実行結果

図418.1に実行結果の動画を示します。

図%%.1
図418.1 オープニングアニメーション

左矢前のブログ 次のブログ右矢

オープニングアニメーション (2)

posted by sakurai on June 10, 2021 #417

データ構造

以下にデータ構造を示します。

図%%.1
図417.1 構造体配列

文字一つに対するパラメータは、図417.2のとおりです。

{sx, sy, dx, dy, w, h} = {パターンソースx, パターンソースy, デスティネーションx, デスティネーションy, 幅、高さ}

本来は図417.1及び図417.2のような構造体の配列としたかったのですが、bscでエラーとなるため、やむなく要素毎の配列に分解しました。構造体配列の実現は現在問い合わせ中です。

BSVコード

以下に分解したコードを示します。

UInt#(8) s1sx[19] = { 42, 50, 58, 77, 66, 42, 58, 74, 82, 42, 42, 90, 98,106, 58,114, 82,122, 66},
         s1sy[19] = {137,137,137,126,137,137,137,137,137,129,129,137,137,137,137,137,137,137,137},
         s1dx[19] = {112,120,128,136, 72, 80, 88, 96,104,112,120,128,136,144,152,160,168,176,184},
         s1dy[19] = { 68, 68, 68, 68, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93},
         s1w[19]  = {  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5},
         s1h[19]  = {  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7};

UInt#(8) s2sx[23] = { 42, 50, 58, 66, 74, 82, 90, 98,106, 90,114, 58, 82,122, 90, 82, 90, 82, 42, 52, 68, 51,  2},
         s2sy[23] = {145,145,145,145,145,145,145,145,145,145,145,145,145,145,145,162,162,145,145, 16, 64, 80, 80},
         s2dx[23] = { 48, 56, 64, 72, 80, 88,104,112,120,128,136,144,152,168,176,184,192,200,208, 76, 80, 79, 78},
         s2dy[23] = {125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,142,155,169,184},
         s2w[23]  = {  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5, 16,  8, 11, 12},
         s2h[23]  = {  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  8,  8,  8};

UInt#(8) s3sx[40] = { 98,106, 42,114,122, 82, 90, 98,106,122, 98,  1,  9, 42,114,122, 82, 90, 90, 82, 98,106,114, 42, 82, 90, 98,106,114,122, 98,122,114, 42, 82, 90, 98,106,114,122},
         s3sy[40] = {162,162,129,162,162,170,170,170,170,162,162,154,154,129,170,170,178,178,170,170,178,178,178,129,186,186,186,186,186,186,178,178,178,129,186,186,186,186,186,186},
         s3dx[40] = { 96,104,112,120,128,136,144,152,160,168, 96,104,112,120,128,136,144,152,160,168, 96,104,112,120,128,136,144,152,160,168, 96,104,112,120,128,136,144,152,160,168},
         s3dy[40] = {142,142,142,142,142,142,142,142,142,142,156,156,156,156,156,156,156,156,156,156,170,170,170,170,170,170,170,170,170,170,184,184,184,184,184,184,184,184,184,184},
         s3w[40]  = {  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5},
         s3h[40]  = {  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7};

"SPACE INVADERS"の文字列を、最初は"SPACEINVADERS"とタイミングを空けずに場所を空けて表示していました。が、Youtubeを見ると、"SPACE△△INVADERS"と、空白(△)は場所だけでなくタイミングも空いているようだったので、文字列も"SPACE△△INVADERS"と設定しています。

もうひとつ注意点として、オープニングアニメーション中のウエイト中にSキー(Sボタン)が押された場合は直ちにゲームスタートとなることです。


左矢前のブログ 次のブログ右矢

オープニングアニメーション

posted by sakurai on June 9, 2021 #416

Youtube

この動画を観察すると、オープニングアニメーションの際に文字が一文字ずつ表示されているので、これを実装します。文字列は3つの部分から構成されています。

  • "PLAY SPACE INVADERS" --- 一文字ずつゆっくりと表示
  • "*SCORE ADVANCE TABLE *..." --- 一瞬で表示
  • "=? MYSTERY..." --- 一文字ずつゆっくりと表示

これを3つのカタマリとして、3つの表示ルーチンで表示します。最初と最後のルーチンは1文字表示するたびに8/60 sec(=133.3 msec)のウエイトを入れています。文字表示の間にsbuttonを見ているのは、スタートボタンにより、いつでもゲームを開始できるように割り込みを入れるためです。

function Stmt stringS1; // PLAY SPACE INVADERS
   return (seq
      for (str_idx <= 0; str_idx < 19; str_idx <=  str_idx + 1) seq
         copyArea(s1sx[str_idx], s1sy[str_idx], s1dx[str_idx], s1dy[str_idx], s1w[str_idx], s1h[str_idx]);
         wait_timer(`TICK_WAIT8);
         if (sbutton) break;
      endseq
   endseq);
endfunction

 function Stmt stringS2; // *SCORE ADVANCE TABLE* ...
    return (seq
      for (str_idx <= 0; str_idx < 23; str_idx <=  str_idx + 1) seq
         copyArea(s2sx[str_idx], s2sy[str_idx], s2dx[str_idx], s2dy[str_idx], s2w[str_idx], s2h[str_idx]);
      endseq
   endseq);
endfunction

function Stmt stringS3; // =? MYSTERY ...
   return (seq
      for (str_idx <= 0; str_idx < 40; str_idx <=  str_idx + 1) seq
         copyArea(s3sx[str_idx], s3sy[str_idx], s3dx[str_idx], s3dy[str_idx], s3w[str_idx], s3h[str_idx]);
         wait_timer(`TICK_WAIT8);
         if (sbutton) break;
      endseq
   endseq);
endfunction

左矢前のブログ 次のブログ右矢

ソフトブロック解説 (5)

posted by sakurai on June 8, 2021 #415

sound階層

サウンド階層には4つの独立したサウンドステートマシンの他、サウンドミキサーやパラシリモジュールが存在します。4つの独立したサウンドステートマシンにより、同時に4音の発声が可能となっています。

図%%.1
図415.1 sound階層

サウンドステートマシンサブ階層

図415.2にサウンドステートマシンサブ階層を示します。このステートマシンが4チャネルあります。

  • mkSoundFSMモジュール --- サウンドROMを読み出すステートマシンであり、それをミキサーに出力する (BSV⇒Verilog)
  • サウンドROM --- Waveフォーマットデータを格納するROM (Xilinx IP)

図%%.2
図415.2 soundFSM階層

ミキサー&パラシリモジュール

  • ミキサーモジュール --- 4個の独立したサウンドFSMからの音データを加算し重畳する (Verilog)
  • パラシリモジュール --- ミックス後のパラレルデータをシリアルに変換し、シリアルDACに出力する (Verilog)

図%%.3
図415.3 ミキサー&パラシリ階層

コマンドバッファサブ階層

最後にコマンドバッファサブ階層を示します。

  • コマンドバッファ(OneStage) --- GameFSMとSoundFSMの間でコマンドを受け渡す (Verilog)

図%%.4
図415.4 コマンドバッファ階層

左矢前のブログ 次のブログ右矢

ソフトブロック解説 (4)

posted by sakurai on June 7, 2021 #414

graphics階層

基本的にはgraphic_controlモジュールが中心となる階層です。次の3つのモジュールから構成されています。

  • graphic_controlモジュール --- クロックとリセットを入力し、HS, VS, DT等のグラフィックディスプレイタイミングやVRAMアドレスを生成するモジュール (Verilog)
  • xslice --- VRAMデータ中のRGB成分のみを抜き出す (Xilinx IP)。VRAMはRGBの他にもう一面持っており、その面にはシールドデータのみが描画されています。しかしながら、その面は表示はされません。この情報は、自弾及び敵弾の衝突判定に用います。これによりシールドをピクセル毎に破壊することができます。

    図%%.1
    図414.1 ピクセル毎の衝突判定
  • display_outモジュール --- ディスプレイタイミング時に表示データを出力し、さらに爆発信号EXPにより全面赤表示にするモジュール (Verilog)

図%%.2
図414.2 graphics階層

左矢前のブログ 次のブログ右矢

ソフトブロック解説 (3)

posted by sakurai on June 4, 2021 #413

VRAM階層

VRAM階層は、図413.1で示すように以下の2モジュールから構成されます。

  • VRAM --- 256×256×4bitのデュアルポートメモリ (Xilinx IP)
  • Muxモジュール --- メモリダンプモジュールからのアドレスをマルチプレクスする (BSV⇒Verilog, 過去記事で設計)

図%%.1
図413.1 VRAM階層

左矢前のブログ 次のブログ右矢

ソフトブロック解説 (2)

posted by sakurai on June 3, 2021 #412

invader階層

引き続きinvader階層です。これはインベーダゲームの中心となる、GameFSM(invader_move)を含む階層です。基本的には、

  • GameFSMモジュール --- ゲームのシナリオを実行するFSM (BSV⇒Verilog)
  • パターンROM --- インベーダその他のビットマップを格納するROM (Xilinx IP)

の2つのモジュールにより、VRAMをR/Wすることにより絵を動かしています。この階層には、さらに以下のモジュールが存在します。

  • buttonsモジュール --- FPGAボード上のプッシュボタンと、PMODのジョイスティックインタフェースのOR取り (Verilog)

図%%.1
図412.1 invader階層

左矢前のブログ 次のブログ右矢

ソフトブロック解説

posted by sakurai on June 2, 2021 #411

ソフトブロック解説

ここから、簡単に各ソフトブロック階層の解説をします。全体ブロック図は過去記事に挙げてあります。全体ブロック図において、左から順に、clock階層、メモリダンプモジュール(ソフトブロック階層無し)、invader階層、VRAM階層、graphics階層、sound階層となっています。

clock階層

図411.1にclock階層の構造を示します。使用されているモジュールは全てXilinx IPです。

図%%.1
図411.1 clock階層
入力は
  • sys_clock --- 100MHzクロック
  • reset --- 負論理リセット

の2本です。出力は、

  • C921_6KHz --- 921.6KHzクロックであり、UARTのボーレートクロック
  • C60Hz --- 60Hzクロックであり、動画の1フレーム(tick)を決める基準クロック
  • C2MHz_FSMCLK --- 文字通り2MHzのFSMクロック
  • MCLK --- 11.289MHzクロックであり、サウンド用のマスタークロック
  • C40MHz --- 40MHzクロックであり、グラフィックのドットクロック
  • locked --- 負論理のリセット信号

バイナリカウンタ説明

  • c_counter_binary_0 --- clk_wizからの8MHzクロックを入力し、bit0(4MHz)、bit1(2MHz)、bit2(1MHz)と分周する。xslice_0はそのbit1(2MHz)を取り出し、C2MHz_FSMCLKとして出力する。
  • c_counter_binary_1 --- c_counter_binary_0からxslice_2はそのbit2(1MHz)を取り出し、c_counter_binary_1で0x411a(=16666)を計数したらリセットする。xslice_1ではその14bit(16.667msec=59.9988Hz)を取り出し、C60Hzとして出力する。これはデューティは50%ではないが、エッジを見るため問題ない。
  • c_counter_binary_2 --- clk_wizからの7.3728MHzクロックを入力し、bit0(3.6864MHz)、bit1(1.8432MHz), bit2(921.6KHz)と分周する。xslice_3はそのbit2(921.6KHz)を取り出しC921_6KHzとして出力する。

左矢前のブログ 次のブログ右矢


ページ: