Posts Tagged with "BSV"

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

BSVの例題 (4)

posted by sakurai on June 24, 2024 #823

もともとinuseが不定だったのはバグと言えます。ただ、初期値を設定する代わりに最初にputdownシーケンスを入れたら良いと考え、実行したところうまく動作しました。ついでにChatGPTにコメントを付けてもらいました。以下にソースコードを示します。

ソースコード

//
// CBG SQUANDERER : Dining Philosophers In Bluespec
// (C) 2012 David Greaves, University of Cambridge

// 必要なモジュールをインポート
import StmtFSM::*;

// スプーンのインターフェースを定義
interface Spoon_if;
   method Action pickup;             // スプーンを持ち上げるメソッド
   method Action putdown;            // スプーンを置くメソッド
   method Action putnumber(UInt#(5) value); // スプーンに番号を設定するメソッド
endinterface

// スプーンのモジュールを定義
(* synthesize *)
module spoon (Spoon_if);
   Reg#(Bool) inuse <- mkReg(?);       // スプーンの使用状況を表すレジスタ
   Reg#(UInt#(5)) number <- mkReg(?);  // スプーンの番号を保持するレジスタ

   // スプーンを持ち上げるメソッドの実装
   method Action pickup if (!inuse);
     inuse <= True;
   endmethod

   // スプーンを置くメソッドの実装
   method Action putdown;
     inuse <= False;
   endmethod

   // スプーンに番号を設定するメソッドの実装
   method Action putnumber(UInt#(5) value);
     number <= value;
   endmethod
endmodule

// 哲学者のベンチモジュールを定義
(* synthesize *)
module philoBENCH (Empty);
   // 5つのスプーンインスタンスを作成
   Spoon_if spoon0 <- spoon; 
   Spoon_if spoon1 <- spoon; 
   Spoon_if spoon2 <- spoon; 
   Spoon_if spoon3 <- spoon; 
   Spoon_if spoon4 <- spoon; 

   // 5人の哲学者インスタンスを作成
   Diner_if din0 <- mkDiner(0, 7, 7, spoon0, spoon1);
   Diner_if din1 <- mkDiner(1, 6, 4, spoon2, spoon1); // <---- 逆順で持ち上げる
   Diner_if din2 <- mkDiner(2, 5, 9, spoon2, spoon3);
   Diner_if din3 <- mkDiner(3, 6, 6, spoon3, spoon4);
   Diner_if din4 <- mkDiner(4, 8, 8, spoon4, spoon0);

   // タイマーのレジスタを作成
   Reg#(UInt#(15)) timer <- mkReg(1000);

   // タイマーをカウントダウンするルール
   rule foo;
      timer <= timer - 1;
      if (timer == 0) $finish;
   endrule
endmodule: philoBENCH

// ランダム数生成器のインターフェースを定義
interface Random_if;
   method ActionValue#(UInt#(15)) gen; // ランダムな値を生成するメソッド
endinterface    

// ランダム数生成器のモジュールを定義
module mkRandom_gen #(UInt#(15) seed) (Random_if);
   Reg#(UInt#(15)) prbs <- mkReg(seed); // ランダム数を保持するレジスタ

   // ランダムな値を生成するメソッドの実装
   method ActionValue#(UInt#(15)) gen;
      prbs <= (prbs << 1) | (((prbs >> 14) ^ (prbs >> 13)) & 1); // シフトとXORを使用してランダム値を生成
      return prbs;
   endmethod
endmodule 

// 哲学者のインターフェースを定義
interface Diner_if;
endinterface

// 哲学者のモジュールを定義
module mkDiner #(UInt#(5) number, UInt#(15) on, UInt#(15) seed) (Spoon_if left, Spoon_if right, Diner_if i);
   Reg#(Bool) eating <- mkReg(?);      // 食事中かどうかを表すレジスタ
   Reg#(UInt#(15)) timer <- mkReg(0);  // タイマーのレジスタ
   Random_if random<- mkRandom_gen(seed); // ランダム数生成器のインスタンスを作成

   // タイマーをカウントダウンするルール
   rule foo (timer != 0);
      timer <= timer - 1;
   endrule

   // 哲学者の動作シーケンスを定義
   Stmt seq_behaviour = (seq
      while (True) seq
         action // 初期状態を確定するサイクル(食事の後だったのを前に持ってきた)
            eating <= False;
            left.putdown; 
            right.putdown;
            left.putnumber(?);
            right.putnumber(?);
         endaction
         action
            UInt#(15) x <- random.gen;
            timer <= x & 31; // ランダムな時間を設定
         endaction
         await(timer== 0);
         left.pickup; // 左のスプーンを持ち上げる
         action
            UInt#(15) x <- random.gen;
            timer <= x & 31; // ランダムな時間を設定
         endaction
         await(timer== 0);
         right.pickup; // 右のスプーンを持ち上げる
         action
            eating <= True; // 食事を開始
            timer <= on;    // 食事時間を設定
            left.putnumber(number);  // スプーンに哲学者の番号を設定
            right.putnumber(number); // スプーンに哲学者の番号を設定
         endaction
         await(timer==0);
      endseq
   endseq);

   // シーケンスを自動的に実行するFSMを作成
   mkAutoFSM(seq_behaviour);
endmodule

以下にシミュレーション波形を示します。putdownは終了処理だった(pickup→putdown)ものをシーケンスの最初に持ってくる(putdown→pickup)ことにより、初期状態が確定し不定が継続しなくなりました。

図%%.1
図823.1 verilogシミュレーション

図らずも元のコードのバグを発見しましたが、あえて不定を設定しないと見つからないため、シミュレーションは不定も込みで実施したほうが良いと思います。


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

BSVの例題 (3)

posted by sakurai on June 21, 2024 #822

どの哲学者がどのスプーンを使っているかを見やすくするため、スプーンを使用中に哲学者の番号を付けることにします。初期状態は'X'ですが、bsvのシミュレータであるbsimでは'X'は取れないようです。

$ bsc -u -opt-undetermined-vals -unspecified-to X -no-warn-action-shadowing -sim philo.bsv
$ bsc -sim -e philoBENCH -o philoBENCH.exe
$ ./philoBENCH.exe -V bsim.vcd
$ gtkwave -A bsim.vcd

図%%.1
図822.1 bsimシミュレーション

spoonのナンバーを見ると、哲学者の取得するスプーンの様子が良くわかります。最初に1番の哲学者が(1, 2)を用いて食事し、次に0番が(0, 1)、4番が(0, 4)、3番が(3, 4)とたまたま順に食事します。次に2番が(2, 3)を使用し、ほぼ同時に4番が(0, 4)を用いて食事をしています。

verilogシミュレーション

$ bsc -u -opt-undetermined-vals -unspecified-to X -no-warn-action-shadowing -verilog philo.bsv
$ bsc -verilog -e philoBENCH -o philoBENCH.exev
$ ./philoBENCH.exev +bscvcd=verilog.vcd
$ gtkwave -A verilog.vcd

図%%.2
図822.2 verilogシミュレーション

最初に1番の哲学者が(1, 2)を用いて食事し、次に0番が(0, 1)、4番が(0, 4)、3番が(3, 4)とたまたま順に食事します。次に2番が(2, 3)を使用し、ほぼ同時に4番が(0, 4)に食事をしています。不定が赤なので確定信号が良く分かります。

なお当初全面Xになって動作しなかったのですが、原因はspoonモジュールのinuseの初期値がXとなっていたためでした。Falseにしたところ正しく動作しました。

(* synthesize *)
module spoon (Spoon_if) ;
   Reg#(Bool) inuse <- mkReg(?);
   method Action pickup if (!inuse);
     inuse <= True;
   endmethod

この記述で分かるように、inuseの初期値は?(bsvで言うX)にも関わらず、次の行のpickupというメソッドに if (!inuse);条件が付いています。条件が不定なのでpickupメソッドをコールすると結果が不定となり、不定が伝搬することでシミュレーションが真っ赤になってしまいます。


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

BSVの例題 (2)

posted by sakurai on June 20, 2024 #821

食事する哲学者全員が左手→右手の順にスプーンを取るシミュレーションです。当然ですが全員左手にスプーン持った状態でデッドロックします。

図%%.1
図821.1 シミュレーション1

ソースを修正し、2番目の哲学者のみ右手→左手の順のシミュレーションです。この場合はランダム的に全員が食事することができます。

図%%.1
図821.2 シミュレーション2

spoon0とテストベンチは親子モジュールとなっており、その間にpickupとputdownのEN-RDYインタフェースが自動生成されます。まずspoon0にpickup指示を出すと、RDYが落ちます(=busy)。同時にspoon0のinuseがTrueとなり使用中を示します。哲学者の食事が終了するとputdown指示がされ、inuseはFalseになります。

スプーンを取得する場合は当然使用中で無いことを確認してから取得するコードとなっていますが、一方、スプーンをリリースする場合は、直前まで自分が使用していることはシーケンスから明らかなため特に使用中であることは確認していません。


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

BSVの例題

posted by sakurai on June 19, 2024 #820

強化学習シリーズの途中ですが、BSVの例題を見つけたので、紹介します。

「哲学者の食事」をBSVでシミュレーションするものです。

一度にスプーンは片手で1つしか持てず、2つ取ろうとすると、右手⇒左手、もしくは左手→右手の順にスプーンを取るしかありません。ここで、全員が右手⇒左手のように取ると、全員が右手に1つを持ったまま、左側のスプーンが空くのを永久に待つことになり、いわゆるデッドロックが起きます。

ソースは一人の哲学者が他人とは反対の順で取るようにプログラムしてあり、それにより全員がなんらかの形で食事をすることができる様子を示しています。

ソースコード

//
// CBG SQUANDERER : Dining Philosophers In Bluespec
// (C) 2012 David Greaves, University of Cambridge

import StmtFSM::*;

interface Spoon_if;
   method Action pickup;
   method Action putdown;
endinterface

(* synthesize *)
module spoon (Spoon_if) ;
   Reg#(Bool) inuse <- mkReg(?);
   method Action pickup if (!inuse);
     inuse <= True;
   endmethod
   method Action putdown;
     inuse <= False;
   endmethod
endmodule

(* synthesize *)
module philoBENCH (Empty) ;
   Spoon_if spoon0 <- spoon; 
   Spoon_if spoon1 <- spoon; 
   Spoon_if spoon2 <- spoon; 
   Spoon_if spoon3 <- spoon; 
   Spoon_if spoon4 <- spoon; 
   Diner_if din0 <- mkDiner (7, 7, spoon1, spoon0); // <---- Reverse pickup
   Diner_if din1 <- mkDiner (6, 4, spoon1, spoon2);
   Diner_if din2 <- mkDiner (5, 9, spoon2, spoon3);
   Diner_if din3 <- mkDiner (6, 6, spoon3, spoon4);
   Diner_if din4 <- mkDiner (8, 8, spoon4, spoon0);
   Reg#(UInt#(15)) timer <- mkReg(5000);
   rule foo;
      timer <= timer - 1;
      if (timer == 0) $finish;
   endrule
endmodule: philoBENCH

interface Random_if;
   method ActionValue#(UInt#(15)) gen;
endinterface    

module mkRandom_gen #(UInt#(15) seed) (Random_if);
   Reg#(UInt#(15)) prbs <- mkReg(seed);
   method ActionValue#(UInt#(15)) gen;
      prbs <= (prbs << 1) | (((prbs >> 14) ^ (prbs >> 13)) & 1);   
      return prbs;
  endmethod
endmodule 

interface Diner_if;
endinterface

module mkDiner #(UInt#(15) on, UInt#(15) seed) (Spoon_if left, Spoon_if right, Diner_if i) ;
   Reg#(Bool) eating <- mkReg(?);    
   Reg#(UInt#(15)) timer <- mkReg(0);
   Random_if random <- mkRandom_gen(seed);
   rule foo (timer != 0);
      timer <= timer - 1;
   endrule
   Stmt seq_behaviour = (seq
      while (True) seq
         action
            UInt#(15) x <- random.gen;
            timer <= x & 31;
         endaction
         await(timer== 0);
         left.pickup;
         action
            UInt#(15) x <- random.gen;
            timer <= x & 31;
         endaction
         await(timer== 0);
         right.pickup;
         action
            eating <= True;
            timer <= on;
         endaction
         await(timer==0);
         eating <= False;
         left.putdown; 
         right.putdown;
      endseq
   endseq);
   mkAutoFSM(seq_behaviour);
endmodule

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

bsvのMakefile作成 (2)

posted by sakurai on June 12, 2024 #815

トラブルシュート

当初、以下のbsvからverilogをコンパイルするというふつうの依存関係が動作せずに苦労しました。

# Verilogファイルの生成
mk%.v: %.bsv
        time bsc -verilog -u -steps-warn-interval 1000000 -steps 8000000 -suppress-warnings T0054 $<

そこで以下のコマンドによりデバッグ情報を出力したところ、原因がわかりました。

$ make -rd

原因は、makeがtop-original.vという原始ファイルの元となるtop-original.bsvを探しに行ったためでした。探しに行って無ければ無視すれば問題ないのですが、動作としてはこのルール全体を却下する動作をするため、このルールが無効になっていたものです。ChatGPTと相談して.SECONDARYキーワードでなんとか無視させることができました。

パターンマッチ

さらにC/C++等だと拡張子のみが変化するのですが、bsvではUart.bsvをコンパイルするとmkUart.vとなるなど変則的な変化をするため、それがなかなか表現できませんでしたが、

mk%.v: %.bsv

このように変化しないところを%で記述することで対処できました。

原始ファイルからのファイル名生成法

汎用的に使用できるように、原始ファイル名から中間ファイル名を生成するようにしました。例えば、

$(addprefix mk, $(addsuffix .v, $(basename $(wildcard *.bsv))))

これにより原始ファイルのbsvファイル名からverilogファイル名を自動生成します。このようにすればMakefileに具体的なファイル名を書く必要がありません。誤ってwildcard *.v等としてしまうと、make cleanを行った後には何もないためmake処理が正しく行えないので、全部を集めるリンクのような場合は必ず存在するファイルに基づき必要なファイル名を生成します。一方、一対一の場合は%.v: %.bsvのような記法で十分です。

top.vの役割

忘れがちですが、top.vは直下の端子とemacsのマクロにより自動結線するため、テストベンチのverilogであるmkTb.vが必要です。そのため、Makefileにはその依存関係も加えました。

そもそもtop.vはverilog.vcdを出力するためのものです。

$ bsc -verilog -e mkTb -o mkTb.exv

一方、このようにbsimシミュレーションと同様、フラグを-verilogに変えるだけでiverilogを使わずとも*.vファイルをリンクし、実行ファイルまで生成されます。ではなぜtop.vを使ったかというと、verilog.vcdをダンプするためで、mkTb.exvを実行するだけではvcdが出力されなかったからです。

ところが、

$ ./mkTb.exv +bscvcd=verilog.vcd

このフラグ設定によりvcdが出力できることがわかりました。これによってtop.v等は不要となります。従ってemacsで結線する手段やiverilogが不要となるため、Makefileもだいぶ簡潔になります。


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

bsvのMakefile作成

posted by sakurai on June 11, 2024 #814

Makefile

ChatGPTの助けを借りながらbsvのMakefileを作成しました。bsvソースはBSVの問題点?を作成したときのファイルです。完成したMakefileを示します。

# ファイル名の生成
BSRCS = $(wildcard *.bsv) # BSVソースファイル
BASRCS = $(addprefix mk, $(addsuffix .ba, $(basename $(BSRCS)))) # BA中間ファイル
VSRCS = $(addprefix mk, $(addsuffix .v, $(basename $(BSRCS)))) # Verilogファイル

# .PHONY ターゲットの定義
.PHONY: all bsv_view verilog_view clean

# 全体のターゲット定義
all: bsv_view verilog_view

# BSV波形ビューアの起動
bsv_view: bsim.vcd
    gtkwave -A bsim.vcd

# BSV波形ファイルの生成
bsim.vcd: mkTb.exe
    ./mkTb.exe -V bsim.vcd

# BSV実行ファイルの生成
mkTb.exe: $(BASRCS)
    bsc -sim -e mkTb -parallel-sim-link 4 -o mkTb.exe

# BSVファイルのコンパイル
mk%.ba: %.bsv
    time bsc -sim -u -steps-warn-interval 1000000 -steps 8000000 -suppress-warnings T0054 $<

# Verilog波形ビューアの起動
verilog_view: verilog.vcd
    gtkwave -A verilog.vcd

# Verilog波形ファイルの生成
verilog.vcd: mkTb.exv
    ./mkTb.exv -V verilog.vcd

# Verilog実行ファイルの生成
mkTb.exv: top.v $(VSRCS)
    iverilog -y /usr/local/lib/Verilog/ top.v $(VSRCS) -o mkTb.exv

# top.vの生成と更新
top.v: top-original.v mkTb.v
    cp top-original.v top.v
    chmod 644 top.v
    emacs --batch top.v -f verilog-batch-auto

# BSVファイルからVerilogファイルを生成
mk%.v: %.bsv
    time bsc -verilog -u -steps-warn-interval 1000000 -steps 8000000 -suppress-warnings T0054 $<

# クリーンアップ
clean:
    @rm -f mk*.v top.v
    @rm -f *.bi *.bo *.ba a.out \#*
    @rm -f *.cxx *.h *.o *.so *.bexe
    @rm -f *.exe *.exv
    @rm -f *.vcd *~ *.fsdb *.log

# 中間ファイルを保持するための設定
.SECONDARY: top-original.v

依存関係グラフ

ここで、’Makefile'の依存関係の可視化の記事のプログラムをそのまま用いて、

$ LANG=C make -np | python3 make_p_to_json.py > graph.json; python json_to_dot.py workflow.png; xv workflow.png

このコマンドにより作成した依存関係図を図814.1に示します。

図%%.1
図814.1 依存関係図

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

bscのコンパイルとインストール

posted by sakurai on June 10, 2024 #813

bscのコンパイルとインストールをChatGPTの助けを借りながら実行しました。OSはFedora 40の場合です。

$ sudo dnf -y install ghc stack libX11-devel libXft-devel gperf bison flex itk-devel tk-devel itcl-devel tcl-devel g++ iverilog autoconf
$ git clone --recursive https://github.com/B-Lang-org/bsc.git bsc
$ cd bsc

bsc.cabalとstack.yamlを作成します。

$ echo "name:                bsc
version:             0.1.0.0
synopsis:            Brief description
description:         Longer description
license:             BSD3
author:              Author name
maintainer:          example@example.com
category:            Category
build-type:          Simple
cabal-version:       >=1.10
executable bsc
 main-is:             Main.hs
 build-depends:       base >=4.7 && <5
 hs-source-dirs:      src
 default-language:    Haskell2010" > bsc.cabal
$ echo "resolver: lts-18.0
    packages:
    - .
    extra-deps:
    - regex-compat-0.95.2.1
    - syb-0.7.2.4
    - old-time-1.1.0.4
    - split-0.2.5" > stack.yaml

Haskellのパッケージのバージョンを調べるためには、StackageHackageを利用します。ChatGPTに依頼しても調べて貰えます。

次にsrc/Main.hsが無いため作成します。

$ echo 'module Main where
    main :: IO ()
    main = putStrLn "Hello, BSC!"' > src/Main.hs

stackを用いて必要なライブラリをインストールします。

$ stack install regex-compat syb old-time split; stack update; stack build --only-dependencies; stack build
$ stack exec -- make install-src

これでコンパイルが始まるので、しばらく待ちます。終わったら以下のコマンドでインストールします。

$ sudo cp -r ./inst/bin/* /usr/local/bin/; sudo cp -r ./inst/lib/* /usr/local/lib/

何か不明な点があったらリファレンスマニュアルbsv例を調べてからフォーラムで質問します。


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

BSVの問題点? (2)

posted by sakurai on May 27, 2024 #805

Uartをテストベンチに組み込み動作しました。次にFIFO付Uartに改造しようとしたところ、FIFOを接続すると最初に0xAAという未定義データが受信FIFOに入ることがわかりました。

そこでverilogコードを出力し波形観測したところ、確かにリセット直後に0xAAという未定義データがUartから出力され受信FIFOに入力されています。図805.1のようにリセット直後にRDY_readがアサートされていることが判明しました。

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

UartとFIFOを接続しているverilogコードを確認すると、uart$RDY_readはreadメソッドを呼ぶとアサートされ、それによりENQされることが判明しました。

 assign rfifo$ENQ = uart$RDY_read && rfifo$FULL_N ;

最初のFIFO無しの場合に動作していたのは、ステートマシンの値がデコードされていたのでリセット直後の値は無視されていたからです。一方FIFOを接続するとリセット直後からの未定義データもFIFOに入るため、エラーが顕在化したわけです。その理由は、以下のように常にuartをreadしてenqしているためです。

 /* receiver */
 rule forever_rfeceiver_inside;
    let rdata <- uart.read;
    rfifo.enq(rdata);
 endrule

リセット直後にRDY_readが誤って出ているのではないかと言う推測をしました。ここで、readメソッドは以下のようになっており、getfsmという受信fsmの完了信号であるgetfsm.doneによりreadを開始する論理となっています(下記)。

Uart.bsv(変更前):

    method ActionValue#(Bit#(8)) read if (getfsm.done);
       getfsmDone <= False;
       return idata;
    endmethod

getfsm.doneは直接観測できないものの、これがRDY_readとなっているだろうと推測し、getfsm.doneの代わりに相当するgetfsmDone信号に入れ替えてみます(下記)。getfsmDoneを作成しているgetfsmの部分も含めて示します。

Uart.bsv:(変更後)

    Stmt getseq = seq
       await(isdata == 1'h0);
       repeat (8) action
          idata <= {isdata, (idata >> 1)[6:0]};
       endaction
       getfsmDone <= True;
    endseq;
    FSM getfsm <- mkFSM(getseq);

    method ActionValue#(Bit#(8)) read if (getfsmDone);
       getfsmDone <= False;
       return idata;
    endmethod

この修正によりリセット直後のRDY_readは出力されなくなり、FIFOを接続しても正しく動作しました。

まとめるとRDY_readがおかしいというより、初期状態でgetfsm.doneがTrueになる点がおかしいと思われます。

本件についてフォーラムに問い合わせたところ、「mkFSMのdoneメソッドは、startのRDYと等価である。startはリセット後にレディになるので、doneメソッドも trueになる。startが呼び出された後にのみTrueとなる条件が必要な場合は、自分で実装する必要がある。」、つまり初期状態でgetfsm.doneがTrueなのは仕様とのことでした。


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

BSVの問題点?

posted by sakurai on May 24, 2024 #804

Uartを設計中に問題にぶつかりましたが一応解決したのでそれを示します。なお本件はbscサポートフォーラムに投稿済みです。

以下はUartのモジュールとそのテストベンチです。

Uart.bsv:

import StmtFSM::*;

interface Uart_ifc;
   method Bit#(1) sout; // シリアル出力
   (* prefix="" *)
   method Action write((* port="write" *) Bit#(8) nodata); // パラレル入力
   (* prefix="" *)
   method Action sin((* port="sin" *) Bit#(1) nidata); // シリアル入力
   method ActionValue#(Bit#(8)) read; // パラレル出力
   method Bool pdone; // 送信完了フラグ
   method Bool gdone; // 受信完了フラグ
endinterface

(* synthesize, always_ready="sin, sout, pdone, gdone" *)
module mkUart(Uart_ifc);

   // レジスタ定義
   Reg#(Bit#(8)) odata <- mkRegU; // 送信データレジスタ
   Reg#(Bit#(1)) osdata <- mkReg(1'h1); // シリアル出力データレジスタ
   Reg#(Bit#(8)) idata <- mkRegU; // 受信データレジスタ
   Reg#(Bit#(1)) isdata <- mkReg(1'h1); // シリアル入力データレジスタ
   Reg#(Bool) putfsmDone <- mkReg(False); // 送信完了フラグ
   Reg#(Bool) getfsmDone <- mkReg(False); // 受信完了フラグ

   // 送信ステートマシンの定義
   Stmt putseq = seq
      osdata <= 1'h0; // スタートビット
      repeat (8) action
         osdata <= odata[0]; // データを1ビットずつシリアル出力
         odata <= (odata >> 1); // 右シフト
      endaction
      osdata <= 1'h1; // ストップビット
      putfsmDone <= True; // 送信完了フラグをセット
   endseq;
   FSM putfsm <- mkFSM(putseq);

   // 受信ステートマシンの定義
   Stmt getseq = seq
      await(isdata == 1'h0); // スタートビットを待つ
      repeat (8) action
         idata <= {isdata, (idata >> 1)[6:0]}; // データを1ビットずつ受信
      endaction
      getfsmDone <= True; // 受信完了フラグをセット
   endseq;
   FSM getfsm <- mkFSM(getseq);

   // 受信ステートマシンの再起動
   rule restartGetFSM if (getfsm.done);
        getfsm.start;
   endrule

   // シリアル出力
   method Bit#(1) sout;
      return osdata;
   endmethod
   // パラレル入力(データを書き込む)
   method Action write(Bit#(8) nodata) if (putfsm.done);
      odata <= nodata;
      putfsmDone <= False;
      putfsm.start;
   endmethod
   // シリアル入力
   method Action sin(Bit#(1) nidata);
      isdata <= nidata;
   endmethod
   // パラレル出力(データを読み込む)
   method ActionValue#(Bit#(8)) read if (getfsm.done);
//   method ActionValue#(Bit#(8)) read if (getfsmDone);
      getfsmDone <= False; // 受信完了フラグをリセット
      return idata;
   endmethod
   // 送信完了フラグ
   method Bool pdone;
      return putfsmDone;
   endmethod
   // 受信完了フラグ
   method Bool gdone;
      return getfsmDone;
   endmethod
endmodule

このリスト中の以下のコメントアウト部分については後記事で記します。

//   method ActionValue#(Bit#(8)) read if (getfsmDone);

Tb.bsv:

import StmtFSM::*;
import Uart::*;
import Connectable::*;
typedef Bit#(8) Byte;

(* synthesize *)
module mkTb();
   // UARTインターフェースのインスタンスを生成
   Uart_ifc uart <- mkUart;
   // テストバイトレジスタの定義
   Reg#(Byte) tbrReg <- mkRegU;
   
   // UARTの送信と受信を接続
   mkConnection(uart.sout, uart.sin);

   // データを取得する関数
   function Action getData;
      return
         action
            // UARTからデータを読み取り、tbrRegに格納
            let data <- uart.read;
            tbrReg <= data;
         endaction;
   endfunction

   // データを送信する関数
   function Stmt putData(Byte c);
      return (seq
         // UARTにデータを書き込む
         uart.write(c);
      endseq);
   endfunction

   // 応答を確認する関数
   function Stmt checkResp(Byte correctData);
      return (seq
         // データを取得
         getData;
         // 正しいデータと比較
         if (correctData != tbrReg) seq
            // データが異なる場合、表示して終了
            $display("%d->%d", correctData, tbrReg);
            $finish;
         endseq
      endseq);
   endfunction

   // テストシーケンスの定義
   Stmt test = seq
      repeat(20) noAction; // 初期の待機
      putData(1); // データ1を送信
      checkResp(1); // 応答を確認
      putData(2); // データ2を送信
      checkResp(2); // 応答を確認
      putData(3); // データ3を送信
      checkResp(3); // 応答を確認
      putData(4); // データ4を送信
      checkResp(4); // 応答を確認
      putData(8'hff); // データ255を送信
      $display("test OK"); // テスト成功を表示
      repeat(30) noAction; // 終了前の待機
      $finish; // テスト終了
   endseq;
   mkAutoFSM(test); // テストシーケンスを自動化

endmodule

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

BSVのサブインタフェース

posted by sakurai on May 22, 2024 #802

BSVの先進的な話題のひとつにサブインタフェースがあります。これはインタフェースの階層化であり、インタフェース階層に別のインタフェースを入れ子で持つものです。ただしその実装はモジュールの中で定義するので、インタフェースを共通化しつつも実装を変える(ポリモーフィズム)などということが可能です。

DumpFSMモジュール内にUartを内蔵する例を図802.1に示します。

図%%.1
図802.1 モジュール内Uart端子のサブインタフェース化

DUmpFSMモジュールのインタフェース部は、図のように端子を並べることも当然できますが、別の方法としてサブインタフェースを用いて記述することができます。Uartのインタフェースは定義済みなので、端子を書き並べる代わりに再利用します。このように一行で書き表せ、誤りも起こりにくいのがメリットです。

interface DumpFSM_ifc;
:
  interface Uart_ifc uart_ifc; // サブインターフェースの宣言
endinterface

そして、モジュール最後のmethod定義の後にサブインタフェースを定義します。ただし端子の一部しか外部に表れていないため、コンパイル時に外部に出力していないread及びwrite端子が不足する旨の警告が出ます。

   // Uartインタフェースのうち、外部端子に出るもののみを定義
   interface Uart_ifc uart_ifc;
      method Bit#(1) sout; // serial output
         return uart.sout;
      endmethod
      method Action sin(Bit#(1) nidata); // serial input
         uart.sin(nidata);
      endmethod
      method Bool pdone;
         return uart.pdone;
      endmethod
      method Bool gdone;
         return uart.gdone;
      endmethod
   endinterface

コンパイルすると以下の警告が表示されます。

$ bsc -sim DumpFSM.bsv
Warning: "DumpFSM.bsv", line 208, column 14: (T0054)
  Field not defined: `read'
Warning: "DumpFSM.bsv", line 208, column 14: (T0054)
  Field not defined: `write'
Elaborated module file created: mkDumpFSM.ba

ただしread及びwriteは未定義ではなくモジュール内部で使用しており、警告にも関わらず正しく内部で接続されています。


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


ページ: