![]() |
4 |
ソフトブロック解説 (3) |
![]() |
Posts Tagged with "space invaders"
既に発行済みのブログであっても適宜修正・追加することがあります。We may make changes and additions to blogs already published.
![]() |
3 |
ソフトブロック解説 (2) |
![]() |
invader階層
引き続きinvader階層です。これはインベーダゲームの中心となる、GameFSM(invader_move)を含む階層です。基本的には、
- GameFSMモジュール --- ゲームのシナリオを実行するFSM (BSV⇒Verilog)
- パターンROM --- インベーダその他のビットマップを格納するROM (Xilinx IP)
の2つのモジュールにより、VRAMをR/Wすることにより絵を動かしています。この階層には、さらに以下のモジュールが存在します。
- buttonsモジュール --- FPGAボード上のプッシュボタンと、PMODのジョイスティックインタフェースのOR取り (Verilog)

![]() |
2 |
ソフトブロック解説 |
![]() |
ソフトブロック解説
ここから、簡単に各ソフトブロック階層の解説をします。全体ブロック図は過去記事に挙げてあります。全体ブロック図において、左から順に、clock階層、メモリダンプモジュール(ソフトブロック階層無し)、invader階層、VRAM階層、graphics階層、sound階層となっています。
clock階層
図411.1にclock階層の構造を示します。使用されているモジュールは全てXilinx IPです。

- 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として出力する。
スライス値
- xslice_0 --- 4,1,1,1
- xslice_1 --- 15,14,14,1
- xslice_2 --- 4,2,2,1
- xslice_3 --- 4,2,2,1
![]() |
31 |
Space Invadersの構成と物量 |
![]() |
ブロック図
ブロック図をIP Integratorで示します。

リソース使用量
各階層(ソフトブロック)のリソース使用量を図409.2に示します。

表409.1に示すように、BRAMの割合がかなり大きいです。全部で50個中、39.5個を使用しています。
| リソース | 割合[%] |
|---|---|
| MMCM | 20 |
| BUFG | 25 |
| I/O | 16 |
| BRAM | 79 |
| FF | 6 |
| LUT | 36 |
モジュール配置
各階層の配置状況を図409.3に示します。おもしろいことに、サウンドが4つのまとまりに分かれていますが、図409.4のように4つのステートマシン毎に固まっていました。


![]() |
28 |
BSVによる歩行音の改善 |
![]() |
インベーダの歩行音
インベーダの歩行音は、現在は隊列に同期して出力されます。インベーダ一匹の歩行処理は60Hzで実施されるため、初期状態では55/60secに一度、最後の一匹では1/60secに一度歩行音が出力されます。
最後の一匹の時に、どうも歩行音が速すぎると思っていたら、過去記事のとおり、インベーダの数に応じた間隔で歩行音が出力され、必ずしも隊列の動作とは同期していないことが判明しました。アニメーションは、インベーダーの数に比例して徐々に速くなるのに比べ、サウンドは段階的に速くなるのは、どのような意図があるのでしょうか?
なるべくオリジナルに近づけるという方針から、今回はこれを実装します。
歩行音間隔タイマの実装
まず、過去記事の表を実装します。タイマ初期値の最大値は52なので6bitのレジスタが55個必要です。1から55までを使用しており、0を含めた56個を定義しています。
UInt#(6) inv_init_stimer[56] = {5, 5, 7, 9,11,12,13,14,16,16,19,19,19,21,21,21,21,24,24,24,
24,24,28,28,28,28,28,28,34,34,34,34,34,34,34,34,39,39,39,39,
39,39,39,46,46,46,46,46,46,46,52,52,52,52,52,52};
過去記事の歩行音間隔タイマの仕様に示すように、これはタイムアウトした際に、インベーダ数に応じた値でタイマを初期化するための表です。次に、歩行音間隔タイマ本体を実装します。
Reg#(UInt#(6)) inv_stimer <- mkRegU;
次に、サウンドパターンは4から7を繰り返すため、2bitのパターンレジスタを用意します。
Reg#(UInt#(2)) inv_spattern <- mkRegU;
初期化部分です。タイマの初期値は52とします。パターンの初期値は0とします。
inv_stimer <= 52;
inv_spattern <= 0;
隊の先頭で実施していた次のサウンド出力処理を削除します。
// if (inv_ido != END_STATE) seq
// sound(extend(inv_pattern) + 4); // from 4 to 7
// endseq
その代わり、 全てのインベーダについて次の処理を追加します。
// 全てのインベーダについて処理
if (inv_stimer == 0) seq
sound(extend(inv_spattern) + 4); // from 4 to 7
inv_spattern <= inv_spattern + 1;
inv_stimer <= inv_init_stimer[inv_no];
endseq else seq
inv_stimer <= inv_stimer - 1;
endseq
これにより過去記事のような動作を行うはずです。⇒実施したところ、正しく動作したようです。
![]() |
27 |
BSVによるメモリダンプモジュールの設計 (10) |
![]() |
受信したデータは以下の図に示すように、一文字4bitのデータが連続する、VRAM内容を示すログデータです(右側を一部省略)。

VRAMデータ4bitの意味は以下のとおりです。
- bit3: バリケード(シールド)=非画像情報
- bit2: R=画像情報
- bit1: G=画像情報
- bit0: B=画像情報
従って、非画像情報を無視し、次のコードにより画像フォーマットであるPPMに変換します。
log2ppm.c
#include
void main() {
char line[4096];
char ch;
printf("P3\n256 256\n255\n");
for(int y = 0; y <= 255; y++) {
fgets(line, sizeof(line), stdin);
for(int x = 0; x <= 255; x++) {
ch = line[x] - 0x30;
if ((ch & 0x4) != 0) printf("255 "); // R
else printf("0 ");
if ((ch & 0x2) != 0) printf("255 "); // G
else printf("0 ");
if ((ch & 0x1) != 0) printf("255 "); // B
else printf("0 ");
}
printf("\n");
}
}
以下のコマンドによりフィルタを作成します。
$ gcc -O log2ppm.c -o log2ppm
上記のようにフィルターとして実行し、ログデータを画像ファイルに変換します。
$ ./log2ppm putty.ppm
生成されたファイルを画像処理ツールであるgimp2で開くと以下のように正常に受信されています。

以上で、ゲームのメモリダンプ機能がひとおおり完成しました。ゲームの状態を吸い出したのは、これをオートエンコーダによりCNNに認識させるのを目的としています。
![]() |
18 |
インベーダーゲームのソースの研究 (2) |
![]() |
インベーダーゲームのソースの研究を読んで理解したことを記します。
UFOスコア表
参考にしたUFOスコア表です。

BSV実装
この表には重なりがあるので、実は、表は1つで問題ありません。右に8個シフトすると重なることがわかります。

弊社のBSV実装では以下の表を用いています。
UInt#(5) ufo_score[15] = {10,10,10,5,15,10,10,5,5,10,15,10,10,5,30};
ただし、インデックスの初期値を6としており、7番目から使用することで上記のアルゴリズムを再現しています。まず、インデックスの初期化です。
ufo_score_idx <= 6;
次にインクリメント部です。この表は15エントリしかないので、15でラップアラウンドします。
ufo_score_idx <= ufo_score_idx + 1;
if (ufo_score_idx == 15) ufo_score_idx <= 0;
また、インベーダーゲームは得点の1の位は常に0なので、得点は1/10で記憶しています。
オリジナル
さて、ソースコードの研究によると、少々ズレています。

結果として、上記は実装が微妙に異なるものの、最終的には同一の結果となります。
![]() |
17 |
インベーダーゲームのソースの研究 |
![]() |
インベーダーゲームのソースの研究を読んで理解したことを記します。
敵弾速度
参考にしたcellvaderにおいて、敵弾の移動速度は1pixcel/tickでした(tick=1/60sec)。それで特に違和感がなかったのですが、上記資料を見ると、33%も速い4pixcel/3tickとのこと。そのため、3tick毎に4pixcel動かすように修正しますが、注意として衝突判定は1pixcel毎に行う必要があります。そうでないとすり抜けが起きる可能性があります。従って、
タイムフレーム0: 何もしない
タイムフレーム1: 何もしない
タイムフレーム2:
y-1の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
y-2の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
y-3の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
y-4の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
y-4の位置に敵弾移動
この3フレームを繰り返すことになります。さらに、インベーダ―数が8未満の場合は敵弾速度が高速化するとのことです。具体的には5pixcel/3tickとなります。従って以下のようになります。
タイムフレーム0: 何もしない
タイムフレーム1: 何もしない
タイムフレーム2:
y-1の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
y-2の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
y-3の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
y-4の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
y-5の位置で衝突判定、衝突の場合は衝突処理し中断、非衝突の場合は下へ
y-5の位置に敵弾移動
歩行音
フリートトーン(fleet tone)、もしくはステップサウンド(step sounds)と書かれていますが、インベーダーの歩行音のことです。同じく参考にしたcellvaderでは、歩行音の発生間隔は1tone/1fleetでした。つまり隊の動作と歩行音が同期しています。ただし、歩行音の発生毎に4種類の歩行音を切り替えます。
ところが、インベーダーゲームのソースの研究では、隊の移動と歩行音は同期しておらず、歩行音間隔の最小は5だとのことです。具体的な表を示せば、オリジナルのアルゴリズムは以下の表318.1のようになります。確かに、現状の実装ではインベーダー数が1になる場合は歩行音が速すぎる気がします。原文には5未満になると不愉快な音になると書かれています。
| インベーダー数[匹] | 歩行音間隔[tick] |
|---|---|
| 55 | 52 |
| 54 | 52 |
| 53 | 52 |
| 52 | 52 |
| 51 | 52 |
| 50 | 52 |
| 49 | 46 |
| 48 | 46 |
| 47 | 46 |
| 46 | 46 |
| 45 | 46 |
| 44 | 46 |
| 43 | 46 |
| 42 | 39 |
| 41 | 39 |
| 40 | 39 |
| 39 | 39 |
| 38 | 39 |
| 37 | 39 |
| 36 | 39 |
| 35 | 34 |
| 34 | 34 |
| 33 | 34 |
| 32 | 34 |
| 31 | 34 |
| 30 | 34 |
| 29 | 34 |
| 28 | 34 |
| 27 | 28 |
| 26 | 28 |
| 25 | 28 |
| 24 | 28 |
| 23 | 28 |
| 22 | 28 |
| 21 | 24 |
| 20 | 24 |
| 19 | 24 |
| 18 | 24 |
| 17 | 24 |
| 16 | 21 |
| 15 | 21 |
| 14 | 21 |
| 13 | 21 |
| 12 | 19 |
| 11 | 19 |
| 10 | 19 |
| 9 | 16 |
| 8 | 16 |
| 7 | 14 |
| 6 | 13 |
| 5 | 12 |
| 4 | 11 |
| 3 | 9 |
| 2 | 7 |
| 1 | 5 |
そのため、現状での隊(rack)の先頭での歩行音処理を廃止し、次の処理を追加予定です。
- 歩行音間隔タイマーを設置します。
- インベーダー1匹の処理につき、歩行音間隔タイマーを1だけデクリメントし、タイマーがゼロになったら歩行音を鳴らします。
- タイマーがゼロになったら、その時点のインベーダー数で上表を引き、歩行音間隔値をロードします。
インベーダーが減るたびに上表を引いて歩行音間隔値を変更するのではありません。それだとタイマーがゼロになる寸前でインベーダーが死ぬと新たなタイマー値がロードされ、歩行音間隔が約2倍の長さとなる不具合が起きる可能性があります。
歩行音が隊と同期はしていても不快な音にならないように、最小を5にするだけで良いような気もしますが。
⇒ブログ記事で実装完了しました。
![]() |
5 |
ゲームFSMとサウンドFSMの連携 |
![]() |
Ultra96においてBSVで開発
元々Verilog版では、コマンドバッファに書き込むだけで特に何もしなくても動作していました。今回BSVで再設計する際に、サウンドを4chとし、取りこぼしを避けるために考えたのがサウンドキュー(FIFO)でした。

これはVivadoのFIFOジェネレータで作成したため、最小段数でもかなり深く1024段程度となっています。 実験したところ、確かに取りこぼしは無いのですが、一方、サウンドがゲームとズレて行き、まるでサウンドレコーダのような動作になってしまいました。そのため、FIFOを1段に修正しました。FIFOジェネレータでは1段のFIFOは作成できないのでVerilogで記述しました。1段のためFIFOと呼ぶのはおかしいのでコマンドバッファと呼ぶことにします。
コマンドバッファには、ゲームFSMからコマンドが来たことを示すフラグemptyを設け、書き込むと!emptyとなるようにします。サウンドFSMからは!emptyの時に新たにコマンドが来たと判断し、コマンドを読んだ後にemptyに変更します。

Artyボード移植後
Ultra96ではこれで動作していたのですが、Artyボードに移植後に自機増加音が無視されることに気づきました。サウンドFSMが取りこぼしているのだと推測し、再考すると、ゲームFSMが不必要に速いことに気づきました。DSOを接続して調べたところ、96.4%がウェイトだと判明したので、これを1MHzに落としたところ、動作するようでした。
ところが実験すると、依然として自機増加音(コマンドNo.9)が無視されるようです。そこで、ILAを接続して、
- サウンドコマンド
- サウンドFSMステート
- コマンドバッファemtpy
- サウンドFSM内部フラグ(fNO9)
を観測しました。最後の内部フラグfNO9は自機増加音がプリエンプトされないように割込みを禁止するためのフラグで、コマンドNo.9を受け付けた際にTrueになる信号です。

従って、コマンドの書き込みの際にemptyである場合のみ書き込み、!emptyの場合は捨てる処理を行います(図271.4のマゼンタ矢印の処理を追加)。


QSPIフラッシュへの書き込み
通常ではVivadoからPROGRAM AND DEBUG⇒Open Hardware Manager⇒Open Target⇒Auto Connectとし、Program DeviceによりJTAG経由でFPGAにビットストリームを焼きこみます。しかしながらこれだと電源断によりFPGAのSRAM内容が消えてしまいます。また、FPGAプログラミング用のPCが常に必要です。オンボードFlashにデータを焼きこめばPCを持ち運ぶ必要がなく、電源onでアプリケーションが立ち上がるため、Flashのプログラミングを行います。
binファイルの作成
最初にFlashへ書き込むデータファイルであるbinファイルを用意します。これは、Tools⇒Setting(歯車マーク)⇒Project Settings⇒Bitstream画面で行います。この画面を開くと複数のチェックボックスが表示されます。その中の、-bin_file*のチェックボックスにチェックします。

これを行ってから、通常どおりPROGRAM AND DEBUG⇒Generate Bitstreamを実施するとWrite Bitstreamが完了しますが、同時にbinファイルが生成されています。場所はbitファイルと同じところで'プロジェクト/プロジェクト.runs/impl_1/'です。
binファイルの焼きこみ
binファイルができたらProgram Deviceの下のAdd Configuration Memoryにより、Add Configuration Memory Device画面が開きます。Flashデバイスの選択が可能なので、この中で"s25fl128sxxxxx0"を選択します。Search窓にs25fl128を入力すれば、候補が3つ現れますがその真ん中です。

実行
リファレンスマニュアルにはJP1でプログラミングモードが決まるとあります。JP1の位置がどちらでもJTAGからは書き込めるとのことです。初期状態はJP1はショートで、SPI-FLASHのモードとなっており、そのまま電源のOFF⇒ONでSpace Invadersが立ち上がりました。

ページ:


