Posts Tagged with "BSV"

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

verilogシミュレーション

verilogのソースを覗いてみると、FIFO2というモジュールがインスタンスされていました。ソースの一部を示します。

// submodule ifs
FIFO2 #(.width(32'd32), .guarded(32'd1)) ifs(.RST(RST_N),
                                       .CLK(CLK),
                                        .D_IN(ifs$D_IN),
                                        .ENQ(ifs$ENQ),
                                        .DEQ(ifs$DEQ),
                                        .CLR(ifs$CLR),
                                        .D_OUT(ifs$D_OUT),
                                        .FULL_N(ifs$FULL_N),
                                        .EMPTY_N(ifs$EMPTY_N));

ライブラリ中のFIFO2($BLUESPECDIR/Verilog/FIFO2.v)のソースを見ると、中心部分は以下のようになっており、レジスタ2段によるFIFOです。

  data0_reg  <= `BSV_ASSIGNMENT_DELAY
                {width{d0di}} & D_IN | {width{d0d1}} & data1_reg | {width{d0h}} & data0_reg ;
  data1_reg <= `BSV_ASSIGNMENT_DELAY
               d1di ? D_IN : data1_reg ;

ただし、FIFOなので、2段とはいってもレイテンシが必ず2サイクルになるわけではありません。First-In, First-Outなので、1段目に入れたものをデキューしようとすると、そのまま1段目から供給するロジックとなっています。上記のように、data0は入力か、1段目か、0段目の選択となっており、data1が入力か1段目の選択となっています。

assign                 D_OUT = data0_reg ;

であることから、data0が出力側レジスタであることがわかります。一方、data1がD_INを入力とする場合は、d1diという信号がTrueの時であり、その論理は、

wire                   d1di = ENQ & empty_reg ;

となっています。これはFIFOがemtpy(負論理なので)でなく、かつENQされたときというように読めますが、後で条件を調査します。


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

posted by sakurai on December 6, 2022 #564

verilogシミュレーション

ついでにverilogでもシミュレーションしてみます。

$ bsc -verilog Tb.bsv
Verilog file created: mkTb.v
$ iverilog top.v mkTb.v -y /usr/local/lib/Bluespec/Verilog/ -o mkTb.exev
$ ./mkTb.exev
VCD info: dumpfile verilog.vcd opened for output.
------
------
 pc_if = 00000000
------
 pc_if = 00000004
 pc_id = 00000000
------
 pc_if = 00000008
 pc_id = 00000004
 pc_ex = 00000000
------
 pc_if = 0000000c
 pc_id = 00000008
 pc_ex = 00000004
 pc_ma = 00000000
------
 pc_if = 00000010
 pc_id = 0000000c
 pc_ex = 00000008
 pc_ma = 00000004
 pc_wb = 00000000
------
 pc_if = 00000014
 pc_id = 00000010
 pc_ex = 0000000c
 pc_ma = 00000008
 pc_wb = 00000004

のように正しくパイプラインが動作しました。また、gtkwaveで取得したタイムチャートを図564.1に示します。

gtkwファイルはここ

図%-%.1
図564.1 PCパイプラインタイムチャート

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

posted by sakurai on December 5, 2022 #563

bsimシミュレーション

このソースに対して、以下のようにコンパイル、bsimシミュレーションを実行します。

$ bsc -sim Tb.bsv; bsc -sim -e mkTb -o mkTb.exe
Elaborated module file created: mkTb.ba
Bluesim object created: mkTb.{h,o}
Bluesim object created: model_mkTb.{h,o}
Simulation shared library created: mkTb.exe.so
Simulation executable created: mkTb.exe
$ ./mkTb.exe -V
------
------
 pc_if = 0000
------
 pc_if = 0004
 pc_id = 0000
------
 pc_if = 0008
 pc_id = 0004
 pc_ex = 0000
------
 pc_if = 000c
 pc_id = 0008
 pc_ex = 0004
 pc_ma = 0000
------
 pc_if = 0010
 pc_id = 000c
 pc_ex = 0008
 pc_ma = 0004
 pc_wb = 0000
------
 pc_if = 0014
 pc_id = 0010
 pc_ex = 000c
 pc_ma = 0008
 pc_wb = 0004

のように正しくパイプラインが動作しました。また、gtkwaveで取得したタイムチャートを図563.1に示します。

図%-%.1
図563.1 PCパイプラインタイムチャート

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

posted by sakurai on December 3, 2022 #562

BSVのFIFO調査

BSVにおいて、パイプラインレジスタとしてFIFOを使用できるか調査します。まず、PCパイプラインを設計します。PCパイプラインとは、各ステージのPCを保持することで、例えば各ステージにおいてEIT(例外、割り込み、トラップ)が起きた場合、EITが起こった命令アドレスをハンドラに正しく伝えるためのものです。

Tb.bsv


import FIFO::*;

(* synthesize *)
module mkTb (Empty);

   Reg#(int) pc    <- mkReg(0);
   FIFO#(int) ifs    <- mkFIFO;
   FIFO#(int) ids    <- mkFIFO;
   FIFO#(int) exs    <- mkFIFO;
   FIFO#(int) mas    <- mkFIFO;
   FIFO#(int) wbs    <- mkFIFO;

   // <PC>
   rule pc_stage;
      if (pc > 100) $finish(0);
      $display("------");
      ifs.enq(pc);
      pc <= pc + 4;
   endrule

   // <IF>
   rule if_stage;
      let pc_if = ifs.first; ifs.deq;
      $display (" pc_if = %04h", pc_if);
      ids.enq (pc_if);
   endrule

   // <ID>
   rule id_stage;
      let pc_id = ids.first; ids.deq;
      $display (" pc_id = %04h", pc_id);
      exs.enq (pc_id);
   endrule

   // <EX>
   rule ex_stage;
      let pc_ex = exs.first; exs.deq;
      $display (" pc_ex = %04h", pc_ex);
      mas.enq (pc_ex);
   endrule

   // <MA>
   rule ma_stage;
      let pc_ma = mas.first; mas.deq;
      $display (" pc_ma = %04h", pc_ma);
      wbs.enq (pc_ma);
   endrule

   // <WB>
   rule wb_stage;
      let pc_wb = wbs.first; wbs.deq;
      $display (" pc_wb = %04h", pc_wb);
   endrule

endmodule: mkTb

ここで、<PC>は入力レジスタは無いため、ここではFIFOは使用しません(が、後で検討します)。単にPCレジスタを設置するだけです。これは<PC>(PCステージ)のPCということになります。通常はPC<=PC+4ですが、分岐時等ではオフセットが足されます。

各ステージでは、入力されたPCをデキューし、表示し、サイクルの終わりで次のステージにそのPCをエンキューします。

図%-%.1
図562.1 FIFOのDequeuとEnqueueの関係
従って、同一サイクルでエンキューしたものをデキューする(バイパス、パススルー)ことはできません。

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

posted by sakurai on December 2, 2022 #561

画面の縦横の入れ替えの変更

過去記事において、本来VRAMの読み出しアドレスのxとyを入れ替えるつもりが、たまたまwrite側のアドレスマルチプレクサが目につき、それを流用しました。が、動作的な欠点が目立ちました。具体的には、画面書き換えの途中で切り替えると、縦図形と横図形が混在したり、初期画面を動的に準備する負担が大きくなっていました。

これを本来のread側アドレスマルチプレクサに変更します。ただし、write側マルチプレクサに追加してread側マルチプレクサの増設が必要になります。

write側アドレスマルチプレクサは名前をWriteMuxに変更しましたが、元の単なるセレクタに戻しました。新たにReadMuxモジュールを設置し、読み出し側アドレスのxとyを入れ替えます。実際にはyはx(a[7:0])をそのまま用い、xはy(a[15:8])を256から引いたものを用います。

$$ \begin{eqnarray} \left\{ \begin{array}{l} x&\Leftarrow&256-y \\ y&\Leftarrow&x \end{array} \right. \end{eqnarray} $$

図561.1に改造後のブロック図を示します。

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

以下にBSVソースを示します。処理はwrite側で実施した入れ替えとほとんど同様です。

ReadMux.bsv:

// アドレス型の定義
typedef Bit#(16) Addr_t;

// マルチプレクサのインタフェース定義
interface ReadMux_ifc;
   (* prefix="" *)
   // 出力アドレスを生成するメソッド
   method Addr_t outp(Bool sel, Addr_t a);
endinterface

// マルチプレクサの実装
(* synthesize, always_ready = "outp", no_default_clock, no_default_reset *)
module mkReadMux(ReadMux_ifc);

   // 出力アドレスを生成するメソッド
   method Addr_t outp(Bool sel, Addr_t a);
      // xa: aの下位8ビット
      Bit#(8) xa = a[7:0];
      // yax: aの上位8ビットから算出
      Int#(10) yax = 256 - signExtend(unpack(a[15:8]));
      // ya: yaxを8ビットにトリミング
      Bit#(8) ya = truncate(pack(yax));

      // selがTrueなら加工したアドレスを返す
      if (sel) return {xa, ya};
      // selがFalseなら元のアドレスをそのまま返す
      else return a; 
   endmethod

endmodule

この変更により、ボード上のdip swを切り替えることで、リアルタイムに画面の縦横変換ができるようになりました。


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

posted by sakurai on October 18, 2022 #532

図532.1にBSV版のBitpatのREADMEに掲載されていた使用例を示します。

図%%.1
図532.1 BSVのBitpat関数

挙げられた例はちょうどRISC-Vの命令パターンに一致しており、add命令とaddi命令のデコード部分を示したものです。この要領で、次々に他の命令を実装していくことができます。

本ライブラリの動作は以下の2ステップとなっています。

  1. whenの中のパターンマッチでは可変部をvで表し、固定部をnとビットパターンで記述します。vの幅を指定しないで良いのは使いやすそうです。whenの最後に識別した機能を解釈するための関数名を記述します。
  2. マッチした後に呼ばれる関数では、可変部のみを変数で受け(固定部は捨てる)、処理を実行します。結局vの幅は意識しなければなりません。

このように最初に固定部、次に可変部という考え方に慣れる必要があります。最初は使いにくいと感じましたが、慣れれば気にならないのかもしれません。

プロセッサ設計と言ってもパイプラインでなければ、見方を変えれば、RISC-V機械語のインタプリターをHDLで作成するだけなので、それほど難しいことではありません。ひとつずつデコードし、対応する処理を実装していくだけです。この「ハードウェアインタプリター」の1段目はデコードステップで、2段目は実行ステップになります。


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

RISC-Vプロセッサの設計

posted by sakurai on October 17, 2022 #531

「はじめてのCPU自作」という本を購入したので、これを参考にRISC-Vプロセッサを設計します。ただし、この本ではChiselベースとなっていますが、本稿ではBSVベースとします。またパイプラインプロセッサの経験があるため、最初からパイプラインプロセッサを設計します。

さて、この本を読んでいたらChisel(だかScalaだか)にはBitpatという便利な機能があるようです。

図%%.1
図531.1 ChiselのBitpat関数

命令デコーダを書くのに便利そうなので、BSVにもないのか調べたら、GithubにBitpatという似たようなものがありました。これはAlexandre Joannouさんが作成されたものであり、Readmeには以下のように書いています。

BitPat BitPat is a bit-string pattern matching library for Bluespec, inspired by Morten Rhiger's "Type-Safe Pattern Combinators".

BSVにおいてのパターンマッチライブラリとのことです。便利そうなのでRISC-Vの命令デコーダに採用することにします。


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

posted by sakurai on September 27, 2022 #517

完成したゲームのオープニングからのゲーム開始画面です。動画変換フレームレートの関係で、ゼロの点滅がハッキリと再生されませんが、実際にはきれいに点滅しています。

図%%.1
図517.1 オープニングアニメーションシーケンス

実行のシーケンス

  • 得点表(Score Advance Table)アニメーション表示
  • Fボタンを押す
  • "PUSH ONLY 1PLAYER BUTTON"を表示、CREDIT=01
  • Sボタンを押す
  • "PLAY PLAYER<1>"を表示、CREDIT=00、SCORE<1>をゼロにし、規定回数点滅
  • ゲームスタート

図513.5
図513.5 ボタン配置図

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

posted by sakurai on September 26, 2022 #516

Y字リプレースアニメーションのソース

Y字リプレースアニメーションのソースを示します。Y字リプレースアニメーションもFボタンにより中断するため、各所でFボタンを見ています。

function Stmt replaceY;
   return (seq
      // from right to left
      for (i <= 228; i >= 142; i <= i - 2) seq
         copyArea((pack(i)[1] == 1'b1) ? 68 : 84 , 32, i, 67, 10, 8);
         wait_timer(`TICK_WAIT3);
         if (fbutton) break;
      endseq // for
      if (fbutton) break;
      // from left to right
      for (i <= 136; i <= 226; i <= i + 2) seq
         copyArea((pack(i)[1] == 1'b1) ? 75 : 91 , 107, i, 67, 16, 8);
         wait_timer(`TICK_WAIT3);
         if (fbutton) break;
      endseq // for
      eraseArea(226, 67, 16, 8);
      wait_timer(`TICK_WAIT32);
      if (fbutton) break;
      // from right to left
      for (i <= 226; i >= 136; i <= i - 2) seq
         copyArea((pack(i)[1] == 1'b1) ? 77 : 93 , 117, i, 67, 16, 8);
         wait_timer(`TICK_WAIT3);
         if (fbutton) break;
      endseq // for
      wait_timer(`TICK_WAIT32);
      if (fbutton) break;
      eraseArea(141, 67, 9, 8);
      wait_timer(`TICK_WAIT32);
      if (fbutton) break;
   endseq);
endfunction

これだけでなく、タイマールーチンの中でもFボタンによる中断を見ていますが、ちょっとやり過ぎのようです。実際には多少間引いても体感に影響しないと思います。


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

posted by sakurai on September 23, 2022 #515

オープニングアニメーションのソース

オープニングアニメーションのソースを示します。オープニングアニメーションはFボタン(コイン投入の模擬)により中断するため、各所でFボタンを見ています。

function Stmt openingAnimation;
   return (seq
      // Opening Animation
      foa <= True;
      eraseArea( 0, 41, 255, 199); // erase screen
      eraseArea(25,242, 5, 7); // erase zanki
      stringS1; // PLAY ...
      if (fbutton) break;
      wait_timer(`TICK_WAIT64);
      if (fbutton) break;
      stringS2; // *SCORE ...
      if (fbutton) break;
      wait_timer(`TICK_WAIT32);
      if (fbutton) break;
      stringS3; // =? MYSTERY ...
      if (fbutton) break;
      wait_timer(`TICK_WAIT64);
       if (fbutton) break;
      replaceY; // ^ -> Y
      if (fbutton) break;
      wait_timer(`TICK_WAIT64);
      if (fbutton) break;
      foa <= False;
   endseq);
endfunction

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


ページ: