1 |
QSPI Flashへの書き込み |
Posts Tagged with "FPGA"
既に発行済みのブログであっても適宜修正・追加することがあります。We may make changes and additions to blogs already published.
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 <stdio.h> 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.log >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) |
20 |
BSVによるメモリダンプモジュールの設計 (5) |
RAMアドレスマルチプレクサの設計
VRAMアクセスするマスタに、FSM、CRTCに加えてメモリダンパが加わりました。しかしながらBRAMのポートが2つまでなので、FSM側のアドレスバスをシェアします。CRTCは常にアクセスしているのに比べて、メモリダンパはFSMが動作していない時のメモリ状態を観測するためだからです。アドレスシェアのためのマルチプレクサを設計します。
Mux.bsv
typedef Bit#(16) Addr_t; interface Mux_ifc; (* prefix="" *) method Addr_t outp(Bool sel, Addr_t a, Addr_t b); endinterface (* synthesize, always_ready = "outp", no_default_clock, no_default_reset *) module mkMux(Mux_ifc); method Addr_t outp(Bool sel, Addr_t a, Addr_t b); if (sel) return b; else return a; endmethod endmodule
出力のハンドシェーク端子は不要であるため、
(* synthesize, always_ready = "outp" *)
を指定して削除しています。さらに、組み合わせ回路であるため、clock, resetを使用していないので、それらポートを削除するために、
(* no_default_clock, no_default_reset *)
を指定しています。また、入力ピン名が、メソッド名_変数名、例えばoutp_a等のように複雑になるのを防止するため、
(* prefix="" *)
を指定してメソッド名を消しています。
これを合成すると以下のようなVerilogになります。
mkMux.v
// // Generated by Bluespec Compiler (build 38534dc) // // On Thu May 20 14:21:23 JST 2021 // // // Ports: // Name I/O size props // outp O 16 // sel I 1 // a I 16 // b I 16 // // Combinational paths from inputs to outputs: // (sel, a, b) -> outp // // `ifdef BSV_ASSIGNMENT_DELAY `else `define BSV_ASSIGNMENT_DELAY `endif `ifdef BSV_POSITIVE_RESET `define BSV_RESET_VALUE 1'b1 `define BSV_RESET_EDGE posedge `else `define BSV_RESET_VALUE 1'b0 `define BSV_RESET_EDGE negedge `endif module mkMux(sel, a, b, outp); // value method outp input sel; input [15 : 0] a; input [15 : 0] b; output [15 : 0] outp; // signals for module outputs wire [15 : 0] outp; // value method outp assign outp = sel ? b : a ; endmodule // mkMux
わずか、 assign outp = sel ? b : a ; という一行のverilogを得るためにいろいろと記述していますが、これはBSVの練習のためでもあります。
13 |
BSVによるメモリダンプモジュールの設計 (4) |
以下のスクリプトでVerilogシミュレーションを実行します。
bsc -verilog -u Tb.bsv
cp top-original.v top.v
emacs -nw top.v
// emacsでautomodeにより、top.vを生成
iverilog -y /usr/local/lib/Bluespec/Verilog/ top.v mkTb.v mkUart.v -o mkTb.exe
./mkTb.exe
gtkwave -A verilog.vcd
図はちょうど1行を送信したところで、横方向のxが256(xは255までだがチェックサム出力の際に256となる)から0に戻り、縦方向のyが0から1になった時点の波形です。データを0x33, 0x66, 0x0a, 0x30, 0x64と送信しています。
ストップビットを1ビットに削ったところ、1,582,090サイクルとなりました。1アスキーバイトあたり12サイクルなので、8bitの他、スタートが1bit、ストップが3bit相当となっています。送信時間は
- 115,200bpsでは6.9秒
- 230,400bpsでは3.4秒
- 460,800bpsでは1.7秒
- 921,600bpsでは0.9秒
となります。バイナリだとこれの半分の時間となるはずですが、デバッグの都合上アスキーコードの転送とします。
12 |
BSVによるメモリダンプモジュールの設計 (3) |
前稿の疑似コードをBSVに変換します。作成したBSVプログラムを以下に示します。
Tb.bsv
import StmtFSM::*; import mkUart::*; import RegFile::*; typedef Bit#(16) Addr_t; typedef Bit#(4) Data_t; typedef Bit#(8) Byte; (* synthesize *) module mkTb(); RegFile#(Addr_t, Data_t) rom <- mkRegFileLoad("data.hex", 0, 65535); Reg#(Addr_t) address <- mkReg(0); Reg#(Data_t) data <- mkReg(0); Reg#(Byte) byteData <- mkReg(0); Reg#(int) x <- mkReg(0); Reg#(int) y <- mkReg(0); Reg#(Data_t) checksum <- mkReg(0); Uart_ifc uart <- mkUart(); function Stmt nibbleOut(Data_t nibble); return (seq byteData <= extend(nibble) + (nibble >= 10) ? (- 10 + 8'h61) : 8'h30; uart.load(byteData); // $write("%c", byteData); endseq); endfunction: nibbleOut Stmt test = seq for (y <= 0; y <= 255; y <= y + 1) seq checksum <= 0; for (x <= 0; x <= 255; x <= x + 1) seq data <= rom.sub(address); address <= address + 1; checksum <= checksum + extend(data); nibbleOut(data); endseq nibbleOut(truncate(checksum >> 4)); nibbleOut(truncate(checksum)); uart.load(8'h0a); // LF // $display(""); endseq await (uart.done()); $finish; endseq; mkAutoFSM(test); endmodule
ページ: