「BSVによるSpaceInvaderのVerilogソースを公開」と題する記事をQiitaに投稿しました。

ボードの説明、Verilogソースの入手法、VivadoによるFPGAの配置配線までをガイドしています。
「BSVによるSpaceInvaderのVerilogソースを公開」と題する記事をQiitaに投稿しました。
ボードの説明、Verilogソースの入手法、VivadoによるFPGAの配置配線までをガイドしています。
![]() |
17 |
敵弾の貫通度の修正 (3) |
![]() |
このように修正した結果、以下の図423.1に示すように、敵弾の種類により貫通度を変えることができました。
図423.1の下部中央のバリケードの
と深さが異なります。
以下の図はFPGAボードの動画です。最初に右側にRollingショット、次に左側にPlungerショットが着弾しています。
以下の図は前記事のオリジナル動画です。最初に左側にPlungerショット、次に右側にRollingショットが着弾しています。
![]() |
16 |
敵弾の貫通度の修正 (2) |
![]() |
敵弾名はインベーダゲームソースの研究から引用しています。図422.1の左から、Squiggly (抵抗器のようなジグザグ)、Rolling、Plunger (パイプつまり直し)と名前がついています。
図422.2において、敵弾の貫通度は左からそれぞれ3, 3, 7としています。
元々敵弾をランダム(風)に出現させるため、カウンタの値により敵弾タイプを決めていますが、同じように貫通度を決めるテーブルです。
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
![]() |
15 |
敵弾の貫通度の修正 |
![]() |
この動画を観察すると、バリケード(シールド)への貫通度が、敵弾の種類によって異なるように思われます。
下図は左端のインベーダからT字型(Plunger)の敵弾が発射されたところです。
敵弾が左端のバリケードに着弾しました。深く潜って(貫通度=7)爆発しています。
同じく左端のインベーダからジグザグ型(Squiggly)の敵弾が発射されたところです。
敵弾が左端のバリケードに着弾しました。浅く潜って(貫通度=3)爆発しています。明らかに敵弾の種類により貫通度が異なっています。
動画を観察した限りでは、T字型の敵弾が7、他の敵弾は貫通度=3で爆発するようでした。一方、最下端では7で爆発するようなので、バリケードで爆発する際にのみ、貫通度(penetration distance)を設定することにします。
![]() |
11 |
オープニングアニメーション (3) |
![]() |
前々稿に個別ファンクションを掲載したので、それをオープニングアニメーションとしてまとめるファンクションです。オープニングアニメーション時を示すように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に実行結果の動画を示します。
![]() |
10 |
オープニングアニメーション (2) |
![]() |
以下にデータ構造を示します。
文字一つに対するパラメータは、図417.2のとおりです。
{sx, sy, dx, dy, w, h} = {パターンソースx, パターンソースy, デスティネーションx, デスティネーションy, 幅、高さ}
本来は図417.1及び図417.2のような構造体の配列としたかったのですが、bscでエラーとなるため、やむなく要素毎の配列に分解しました。構造体配列の実現は現在問い合わせ中です。
以下に分解したコードを示します。
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ボタン)が押された場合は直ちにゲームスタートとなることです。
![]() |
9 |
オープニングアニメーション |
![]() |
この動画を観察すると、オープニングアニメーションの際に文字が一文字ずつ表示されているので、これを実装します。文字列は3つの部分から構成されています。
これを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
![]() |
8 |
ソフトブロック解説 (5) |
![]() |
サウンド階層には4つの独立したサウンドステートマシンの他、サウンドミキサーやパラシリモジュールが存在します。4つの独立したサウンドステートマシンにより、同時に4音の発声が可能となっています。
図415.2にサウンドステートマシンサブ階層を示します。このステートマシンが4チャネルあります。
最後にコマンドバッファサブ階層を示します。
![]() |
7 |
ソフトブロック解説 (4) |
![]() |
基本的にはgraphic_controlモジュールが中心となる階層です。次の3つのモジュールから構成されています。
xslice --- VRAMデータ中のRGB成分のみを抜き出す (Xilinx IP)。VRAMはRGBの他にもう一面持っており、その面にはシールドデータのみが描画されています。しかしながら、その面は表示はされません。この情報は、自弾及び敵弾の衝突判定に用います。これによりシールドをピクセル毎に破壊することができます。
display_outモジュール --- ディスプレイタイミング時に表示データを出力し、さらに爆発信号EXPにより全面赤表示にするモジュール (Verilog)
![]() |
4 |
ソフトブロック解説 (3) |
![]() |