Posts Tagged with "Design"

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

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


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

posted by sakurai on May 12, 2021 #400

前稿の疑似コードをBSVに変換します。作成したBSVプログラムを以下に示します。

mkTb.bsv

import StmtFSM::*;
import mkUart::*;
import RegFile::*;

typedef Bit#(16) Addr;
typedef Bit#(4) Data;
typedef Bit#(8) Byte;

(* synthesize *)
module mkTb();
      RegFile#(Addr, Data) rom <- mkRegFileLoad("data.hex", 0, 65535);
      Reg#(Addr) address <- mkReg(0);
      Reg#(Data) data <- mkReg(0);
      Reg#(Byte) byteData <- mkReg(0);
      Reg#(int) x <- mkReg(0);
      Reg#(int) y <- mkReg(0);
      Reg#(Data) checksum <- mkReg(0);
      Uart_ifc uart <- mkUart();

      function Stmt nibbleOut(Data 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

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

posted by sakurai on May 11, 2021 #399

上位モジュールの最適化

メモリダンプアルゴリズムが疑似コードにより記述できたので、関数により同じような処理をまとめる最適化を実施します。

疑似コード

int x, y, address;
byte byteData, checksum;

address = 0;
for (y=0 to 255) {
      checksum = 0;
      for (x = 0 to 255) {
            data = memory[address++];
            checksum += data;
            nibbleOut(data);
      }
      nibbleOut(checksum>>4);
      nibbleOut(checksum);
      uart.load(LF);
}

function nibbleOut(nibble) {
      byteData = nibble + (nibble >= 10) ? (- 10 + "a") : "0";
      uart.load(byteData);
}

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

posted by sakurai on May 7, 2021 #398

上位モジュールの設計

1バイトの送信はうまく行ったようなので、上位モジュールmkMemoryDumpを設計します。概略仕様を以下に示します。

  • 1ワードが4bitで構成されるメモリをダンプします。
  • メモリのアドレス0からの内容を65536ワード送信します。
  • 256行×256列で送信し、1行毎にチェックサムを送信します。
  • 1バイトのバイナリは表示可能な2バイトのアスキーコードに変換します。
  • 行末は改行します。

メモリ空間は64Kバイトあります。以下にアルゴリズムをC likeの疑似コードにより示します。

疑似コード

int x, y, address;
byte data, checksum, upper, lower;

address = 0;
for (y=0 to 255) {
      checksum = 0;
      for (x = 0 to 255) {
            data = memory[address++];
            checksum += data;
            data += (data >= 10) ? (-10 + "a") : "0";
            uart.load(data);
      }
      upper = (checksum >> 4) &  8'h0f;
      upper += (upper >= 10) ? (-10 + "a") : "0";
      uart.load(upper);
      lower = checksum &  8'h0f;
      lower += (lower >= 10) ? (-10 + "a") : "0";
      uart.load(lower);
      uart.load(LF);
}

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

BSVによるUARTの設計(7)

posted by sakurai on May 6, 2021 #397

UARTのインタフェース

UARTのポート定義は次の図のようになります。

図%%.1
図397.1 mkUartのport定義

  • データ: load_newdata (8bit, 入力)
  • Ready: RDY_load (1bit, 出力)
  • Enable: EN_load (1bit, 入力)

の3種があり、ReadyとEnableは自動生成されています。ここでReadyはモジュールの受信可能なタイミングを表し、一方EnableはテストベンチからのデータがValidであることを表します。

テストベンチでのモジュール呼び出しは次の図のようになります。

図%%.2
図397.2 mkTbのmkUart呼び出し

シミュレーション波形は次の図のようになります。

図%%.3
図397.3 インタフェースの波形

まずRDY_load(オレンジ)がアサートされて受信可能状態になっているとき、55H()というデータが準備できた際にEN_load()がアサートされます。すると次のサイクルで55H()がUART内部に受け付けられ、同時にRDY_load(オレンジ)がネゲートされ、EN_load()もネゲートされます。

テストベンチはすぐにRDY_load(オレンジ)のアサートを待っていますが通信時にはRDY_load(オレンジ)はネゲートされています。次にRDY_load(オレンジ)がアサートされた時点でEN_load()を出力し同時にデータAAH()を出力します。次のサイクルでデータAAH()が受けつけられます。

このようなハンドシェークが自動的に、つまり誤りなく生成されることもBSVの魅力です。


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

BSVによるUARTの設計(6)

posted by sakurai on May 5, 2021 #396

BSVによるインタフェース生成

BSVによるハンドシェークインタフェース信号の生成を細かく見ていきます。 注目するのは、テストベンチからUARTに与える8ビットデータとそのハンドシェーク信号です。記述はデータ(data)だけですが、ハンドシェーク信号(RDY_data, EN_data)が自動的に生成されます。その様子を図396.1に示します。左側がテストベンチで、右側がUARTモジュールです。

図%%.1
図396.1 ハンドシェーク図

右側のモジュールから見た信号は以下のようになります。

  • データ: data (8bit, 入力)
  • Ready: RDY_data (1bit, 出力)
  • Enable: EN_data (1bit, 入力)

ここでRDY_dataはモジュールの受信可能なタイミングを表し、一方EN_dataはテストベンチからのデータがValidであることを表します。

図%%.2
図396.2 ハンドシェークタイミング図

RDYの確認とENのアサートが同サイクルであることに注意します。通常のFF受けの設計だと、RDYを確認した次のサイクルからENのアサートになりそうですが、ENは次のようにRDYを含む組み合わせ回路により構成されています。そのため、ENはRDYと同タイミングでアサートされます。

図%%.3
図396.3 EN生成回路(部分)

ENはFF受けされるので、組み合わせ回路出力でヒゲが有っても良いという考えなのでしょうか。このほうがレイテンシが短縮されます。


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


ページ: