![]() |
4 |
ソフトブロック解説 (3) |
![]() |
Posts Tagged with "FPGA"
既に発行済みのブログであっても適宜修正・追加することがあります。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として出力する。
![]() |
1 |
QSPI Flashへの書き込み |
![]() |
![]() |
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つのステートマシン毎に固まっていました。


![]() |
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に認識させるのを目的としています。
![]() |
26 |
BSVによるメモリダンプモジュールの設計 (9) |
![]() |
出来上がったモジュールの図を図406.1に示します。

シリアルデータ出力はUART_TXに接続します。これによりFPGAボード(正確にはアダプタボードであるAES-ACC-U96-JTAGボード)上でUSBに変換され、ケーブルを経由してPCのPuttyで受信します。今回は230,400 bps、COMは6番だったので、以下のようにPuttyの受信パラメータ及びログの場所を設定します。


ゲームをスタートさせて、FPGAボード上のダンプスイッチを押すと、ゲームが一時停止し、FPGAボードからはPuttyに対して以下のようなデータを送信して来ます。右端はチェックサムですが、この程度の通信速度ではデータ化けはしていないようです。

![]() |
25 |
BSVによるメモリダンプモジュールの設計 (8) |
![]() |
システム構成図

ハンドシェークアルゴリズム
以下に処理のハンドシェークを示します。
- ボード上のスイッチが押されstart信号が出力される。
- メモリダンパはstartに基づき、sreqを出力し、GameFSMに停止を要求。
- GameFSMは停止要求の有無に関わらず、フレームの最後で60Hzの立ち上がりを待つ。その際にcwaitを出力。
- メモリダンパはcwait(=GameFSMの停止)に基づき、以下のメモリダンプ動作をアドレス分だけ繰り返し。
- selをTrueにしてバス権(アドレス権)を取得
- アドレスを出力
- データを取得
- アスキー化してシリアルデータとして出力
- メモリダンパは終了時にsreqをネゲート。
- GameFSMはsreqがネゲートされるのを待ち、cwaitをネゲートしフレーム先頭から再開。
ゲームFSM側のBSVコードの修正
修正したGameFSM.bsvのウエイトルーチンを示します。
GameFSM.bsv
// 時間待ち
function Stmt wait_timer(
UInt#(12) count
);
return (seq
testOut <= True;
repeat(pack(extend(count))) seq
await(tic == 0);
await(tic == 1 && sreq == 0);
endseq
testOut <= False;
endseq);
endfunction
元々のtest出力信号(wait時を示す)testOut信号をそのままcwait(ゲームFSMのwaitを示す)として使用します。
元々は、60Hzの立下りを
await(tic == 0);
このように待った後、立ち上がりに同期して
await(tic == 1);
このように、ウエイトをリリースする(ウエイトルーチンから抜ける)仕様でした。今回それに加えて、メモリダンプからの停止要求がリリースされていることを
await(tic == 1 && sreq == 0);
このようにAND条件で加えました。これにより、ウエイトしている状態は元々testOut(=cwait)として出力されていたため、それを用いてメモリダンプの開始信号としています。
GameFSMのインタフェースにメモリダンパからの停止要求信号sreqを加えます。
GameFSM.bsv
(* prefix="" *)
method Action sreqm(Bit#(1) in_sreq);
次にワイヤ定義を記述します。
GameFSM.bsv
Wire#(Bit#(1)) sreq <- mkWire;
最後にメソッド定義を示します。
GameFSM.bsv
method Action sreqm(Bit#(1) in_sreq);
sreq <= in_sreq;
endmethod
![]() |
24 |
BSVによるメモリダンプモジュールの設計 (7) |
![]() |
メモリダンプモジュールを組み込むにあたり、前回までテストベンチ(=最上位)であった階層をモジュール化します。外部インタフェースは図404.1のとおりです。

- start (入力): ボード上のスイッチであり、画像ダンプの起動スイッチです。
- sreq (出力): GameFSMに対して(デュアルポートメモリに対して)バス権を要求する信号です。
- cwait (入力): GameFSMが60Hzの同期待ち状態にある信号です。これはバス権を放棄している信号でもあるので、流用します。
- addr (出力): デュアルポートメモリアドレスです。Muxを介してデュアルポートメモリに接続します。
- data (入力): デュアルポートメモリからの4bitデータです。
- sel (出力): Muxの制御信号であり、Trueでデュアルポートメモリのアドレスがメモリダンプモジュール側であることを示します。
- read (出力): シリアルデータ出力です。
入力
(* prefix="" *) // method名を削除するため
method Action startm(Bool newstart);
(* prefix="" *)
method Action datam(Data newdata);
(* prefix="" *)
method Action cwaitm(Bool newcwait);
(* synthesize, always_enabled="startm, datam, cwaitm" *) // EN_xxxを削除するため
出力
method Bool sreqm();
method Addr_t addrm();
method Bool selm();
method Bit#(1) readm();
(* synthesize, always_ready="sreqm, addrm, selm, readm" *) // RDY_xxxを削除するため
入出力は上記のとおりメソッドで定義し、入力はmethod Action、出力はmethodで定義します。
![]() |
21 |
BSVによるメモリダンプモジュールの設計 (6) |
![]() |
ページ: