Posts Tagged with "BSV"

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

ソフトブロック解説 (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として出力する。

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

BSVによる歩行音の改善

posted by sakurai on May 28, 2021 #408

インベーダの歩行音

インベーダの歩行音は、現在は隊列に同期して出力されます。インベーダ一匹の歩行処理は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

これにより過去記事のような動作を行うはずです。⇒実施したところ、正しく動作したようです。


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

posted by sakurai on May 27, 2021 #407

受信したデータは以下の図に示すように、一文字4bitのデータが連続する、VRAM内容を示すログデータです(右側を一部省略)。

図%%.1
図407.1 受信データ(部分)

VRAMデータ4bitの意味は以下のとおりです。

  • bit3: バリケード(シールド)=非画像情報
  • bit2: R=画像情報
  • bit1: G=画像情報
  • bit0: B=画像情報

従って、非画像情報を無視し、次のPHPにより画像フォーマットであるPPMに変換します。

log2ppm.php

 <?php
   print "P3\n256 256\n255\n";
   for($y = 0; $y <= 255; $y++) {
       $line = readline("");
       for($x = 0; $x <= 255; $x++) {
           $ch = hexdec($line[$x]);
           if (($ch & 0x4) != 0) echo '255 ';    // R
           else echo '0 ';
           if (($ch & 0x2) != 0) echo '255 ';    // G
           else echo '0 ';
           if (($ch & 0x1) != 0) echo '255 ';    // B
           else echo '0 ';
       }
       echo "\n";
   }

上記をフィルターとして実行し、ログデータを画像ファイルに変換します。

$ php log2ppm.php <putty.log >putty.ppm

生成されたファイルを画像処理ツールであるgimp2で開くと以下のように正常に受信されています。

図%%.2
図407.2 受信画像

以上で、ゲームのメモリダンプ機能がひとおおり完成しました。ゲームの状態を吸い出したのは、これをオートエンコーダによりCNNに認識させるのを目的としています。


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

posted by sakurai on May 26, 2021 #406

出来上がったモジュールの図を図406.1に示します。

図%%.1
図406.1 モジュールの入出力

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

図%%.2
図406.2 受信パラメータの設定

図%%.3
図406.3 ログの場所の設定

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

図%%.4
図406.4 受信データ(部分)


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

posted by sakurai on May 25, 2021 #405

システム構成図

図%%.1
図405.1 システム構成図

ハンドシェークアルゴリズム

以下に処理のハンドシェークを示します。

  • ボード上のスイッチが押され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

元々は、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

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

posted by sakurai on May 24, 2021 #404

メモリダンプモジュールを組み込むにあたり、前回までテストベンチ(=最上位)であった階層をモジュール化します。外部インタフェースは図404.1のとおりです。

図%%.1
図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 addrm();
method Bool selm();
method Bit#(1) readm();

(* synthesize, always_ready="sreqm, addrm, selm, readm" *)   // RDY_xxxを削除するため

入出力は上記のとおりメソッドで定義し、入力はmethod Action、出力はmethodで定義します。


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

posted by sakurai on May 21, 2021 #403

前稿で設計したアドレスマルチプレクサを組み込んだVRAMの構成図を図403.1に示します。

図%%.1
図403.1 VRAMモジュール

selはa側のアドレスについて、a1(GameFSM)側を使用するかa2(DumpFSM)側を使用するかを決定する信号です。


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

posted by sakurai on May 20, 2021 #402

RAMアドレスマルチプレクサの設計

VRAMアクセスするマスタに、FSM、CRTCに加えてメモリダンパが加わりました。しかしながらBRAMのポートが2つまでなので、FSM側のアドレスバスをシェアします。CRTCは常にアクセスしているのに比べて、メモリダンパはFSMが動作していない時のメモリ状態を観測するためだからです。アドレスシェアのためのマルチプレクサを設計します。

図%%.1
図402.1 マルチプレクサ

mkMux.bsv

typedef Bit#(16) Addr;

interface Mux_ifc;
   (* prefix="" *)
   method Addr outp(Bool sel, Addr a, Addr b);
endinterface

(* synthesize, always_ready = "outp", no_default_clock, no_default_reset *)
module mkMux(Mux_ifc);
   method Addr outp(Bool sel, Addr a, Addr 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の練習のためでもあります。


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

posted by sakurai on May 13, 2021 #401

以下のスクリプトでVerilogシミュレーションを実行します。

bsc -verilog -u mkTb.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 mkTb.vcd

図%%.1
図401.1 シミュレーション波形

図はちょうど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秒

となります。バイナリだとこれの半分の時間となるはずですが、デバッグの関係で、当面アスキーコードの転送とします。


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


ページ: