Posts Tagged with "BSV"

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

BSVによるUARTの再設計 (7)

posted by sakurai on August 24, 2026 #1112

ブロック図を示します。

図1112.1

シミュレーション波形を示します。

図1112.2

各サイクル間の動きです。表現はサイクル中に発現するデータであり、確定は次のサイクルの頭となります。

  • n:送信stop bit, 受信最終bit, putfsm.done,
  • n+1: 受信stop bit
  • n+2: getfsm.done
  • n+3: topでデータ取得、チェック, getfsm.start
  • n+4: putfsm.start
  • n+5: 送信start bit

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

BSVによるUARTの再設計 (6)

posted by sakurai on August 23, 2026 #1111

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
      putData(8'haa); // データ1を送信
      checkResp(8'haa); // 応答を確認
      putData(8'h55); // データ2を送信
      checkResp(8'h55); // 応答を確認
      putData(8'hc3); // データ3を送信
      checkResp(8'hc3); // 応答を確認
      putData(8'h3c); // データ4を送信
      checkResp(8'h3c); // 応答を確認
      $display("test OK"); // テスト成功を表示
      await(uart.pdone);
      $finish; // テスト終了
   endseq;
   mkAutoFSM(test); // テストシーケンスを自動化

endmodule

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

BSVによるUARTの再設計 (5)

posted by sakurai on August 22, 2026 #1110

次にUartを送受信化します。送信部のFSMと並行で動作する受信部のFSMを設計します。

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; // 送信完了フラグ
endinterface

(* synthesize, always_ready="sin, sout, pdone" *)
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) getfsmDone <- mkReg(False); // 受信完了フラグ

   // 送信ステートマシンの定義
   Stmt putseq = seq
      osdata <= 1'h0; // スタートビット
      repeat (8) action
         osdata <= odata[0]; // データを1ビットずつシリアル出力
         odata <= (odata >> 1); // 右シフト
      endaction
      osdata <= 1'h1; // ストップビット
   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;
      putfsm.start;
   endmethod
   // シリアル入力
   method Action sin(Bit#(1) nidata);
      isdata <= nidata;
   endmethod
   // パラレル出力(データを読み込む)
   method ActionValue#(Bit#(8)) read if (getfsmDone);
      getfsmDone <= False; // 受信完了フラグをリセット
      return idata;
   endmethod
   // 送信完了フラグ
   method Bool pdone;
      return putfsmDone;
   endmethod
endmodule

送受信のFSMにおいて、起動のタイミングが非対称的です。

  • 送信FSMの起動は上位からパラレル入力をロードされてパラシリが開始する
  • 受信FSMはシリアルデータがいつくるかわからないので、常に起動されていなくてはならない

従って、送信側は

   // パラレル入力(データを書き込む)
   method Action write(Bit#(8) nodata) if (putfsm.done);
      odata <= nodata;
      putfsm.start;
   endmethod

このように、データが書き込まれた契機でfsmをスタートさせるのに対し、受信側は

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

FSMの終了を待ってその起動をかけるルールを追加しています。これはAutoFSMとして自動起動にすることも可能です。


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

BSVによるUARTの再設計 (4)

posted by sakurai on August 21, 2026 #1109

最後にシミュレーション結果を示します。

シミュレーションの最初はbluesimによるものです。

図1109.1
図1109.1 bluesimシミュレーション

次はiverilogによるものです。テストベンチとUartの間のインタフェースのEN/RDY信号が見えています。

図1109.2
図1109.2 verilogシミュレーション

次はUartの完了待ち合わせをdoneを待つことではなく、コール待ちに変更したものです。

図1109.3
図1109.3 bluesimシミュレーション(drain化)

コール待ちであるEN_drainが見えています。

図1109.4
図1109.4 verilogシミュレーション(drain化)


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

BSVによるUARTの再設計 (3)

posted by sakurai on August 20, 2026 #1108

次にMakefileを示します。

# BSV simulation Makefile (done版 / drain版 併存)
#   done版  : Tb.bsv + Uart.bsv           → run / wave / vrun / vwave
#   drain版 : TbDrain.bsv + UartDrain.bsv → drain-run / drain-wave / drain-vrun / drain-vwave
#   make clean : 生成物の削除

.PHONY: all run wave vrun vwave drain-run drain-wave drain-vrun drain-vwave clean

all: run

# ==== done版 ====

# ---- Bluesim ----
Tb.exec: Tb.bsv Uart.bsv
	bsc -u -sim Tb.bsv
	bsc -sim -e mkTb -o Tb.exec

bsim.vcd: Tb.exec
	./Tb.exec -V bsim.vcd

run: bsim.vcd

wave: bsim.vcd
	gtkwave -A bsim.vcd &

# ---- Verilog sim ----
mkTb.exev: Tb.bsv Uart.bsv
	bsc -u -verilog Tb.bsv
	bsc -verilog -e mkTb -o mkTb.exev

verilog.vcd: mkTb.exev
	./mkTb.exev +bscvcd=verilog.vcd

vrun: verilog.vcd

vwave: verilog.vcd
	gtkwave -A verilog.vcd &

# ==== drain版 ====

# ---- Bluesim ----
TbDrain.exec: TbDrain.bsv UartDrain.bsv
	bsc -u -sim TbDrain.bsv
	bsc -sim -e mkTbDrain -o TbDrain.exec

bsim_drain.vcd: TbDrain.exec
	./TbDrain.exec -V bsim_drain.vcd

drain-run: bsim_drain.vcd

drain-wave: bsim_drain.vcd
	gtkwave -A bsim_drain.vcd &

# ---- Verilog sim ----
mkTbDrain.exev: TbDrain.bsv UartDrain.bsv
	bsc -u -verilog TbDrain.bsv
	bsc -verilog -e mkTbDrain -o mkTbDrain.exev

verilog_drain.vcd: mkTbDrain.exev
	./mkTbDrain.exev +bscvcd=verilog_drain.vcd

drain-vrun: verilog_drain.vcd

drain-vwave: verilog_drain.vcd
	gtkwave -A verilog_drain.vcd &

clean:
	rm -f *.bo *.ba *.cxx *.h *.o *.so *.exec *.exev mk*.v *.vcd *~

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

BSVによるUARTの再設計 (2)

posted by sakurai on August 19, 2026 #1107

インタフェースを変えたため、呼び出し元のコードで書けば、元が

Tb.bsv

      Stmt test = seq
            uart.load(8'h55);
            uart.load(8'haa);
            uart.load(8'hc3);
            uart.load(8'h3c);
            await (uart.done());
            $finish;
      endseq;

これが

      Stmt test = seq
            uart.load(8'h55);
            uart.load(8'haa);
            uart.load(8'hc3);
            uart.load(8'h3c);
            uart.drain();
            $finish;
      endseq;

このようになります。大きなメリットは無いものの、AI曰く、

  • 第一に情報隠蔽の向きです。done 版は「完了したか」という述語をモジュールの外に公開し、それをどう使うか(待つのか、覗くだけか)は呼び側の責任で、await を書き忘れる余地があります。drain 版は待ち条件をモジュール側の呼出規約に畳み込むので、呼び側は呼ぶことしかできず、誤用の形が存在しません。
  • 第二に語彙の統一で、done 版の Tb は「呼出4回+述語待ち1回」の2語彙、drain 版は「呼出5回」の1語彙になります。
  • 第三に使い分けの指針も書けて、複数箇所から自由に覗きたい状態監視なら値メソッド(done)、シーケンスの合流点なら空アクション(drain)、という整理です。RUN_FSM マクロが await(F.done) を使っているのは前者の側で、これを後者に寄せる余地がある、という接続もできます。

呼び出し側の合成後の回路も同等で、前者が

  // rule RL_action_l17c18                                                                
  assign WILL_FIRE_RL_action_l17c18 =
             uart$done && state_mkFSMstate == 4'd4 ;

となるのに対し、後者は

  // rule RL_action_l17c17                                                                
  assign WILL_FIRE_RL_action_l17c17 =
             uart$RDY_drain && state_mkFSMstate == 4'd4 ;

となり、呼び出し側でも同一論理であることがわかります。前者はuartのdone、つまりfsmのdoneをawaitするという論理を発火論理に組み込むのに対して(awaitなどは消えている)、後者はdrainをコールするための自動バックプレッシャのRDYを発火論理に組み込むという違いとなります。


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

BSVによるUARTの再設計

posted by sakurai on August 18, 2026 #1106

Uart.bsvを若干修正しました。理由は、過去記事では呼び出しにdone=fsm.doneを返すようにしていたものを、ダミー関数drainの実行待ちでwaitさせるものです。

UartDrain.bsv

// UartDrain.bsv : drain版UART
// Uart.bsv (done版) との差分は done() → drain() のみ
package UartDrain;

import StmtFSM::*;

interface Uart_ifc;
      method Bit#(1) read();
      method Action load(Bit#(8) newdata);
      method Action drain();
endinterface

(* synthesize *)
module mkUartDrain(Uart_ifc);
      Reg#(Bit#(8)) data  <- mkReg(8'haa);
      Reg#(Bit#(1)) odata <- mkReg(1);

      Stmt s = seq
            odata <= 1'h0; // start bit
            repeat (8) action
                  odata <= data[0];
                  data <= (data >> 1);
            endaction
            odata <= 1'h1; // stop bit
      endseq;

      FSM fsm <- mkFSM(s);

      method Bit#(1) read();
            return odata;
      endmethod

      method Action load(Bit#(8) newdata) if (fsm.done);
            data <= newdata;
            fsm.start;
      endmethod

      // ダミー関数: 完了を暗黙条件とする空アクション
      // RDY_drain = fsm.done、EN_drain は内部で未使用
      method Action drain() if (fsm.done);
            noAction;
      endmethod
endmodule

endpackage

UARTモジュールの合成後の回路は同等であり、前者が

  // value method done
  assign done =
	     fsm_abort_whas_AND_fsm_abort_wget_OR_fsm_state_ETC___d71 &&
	     !fsm_start_reg ;

となり、呼び出し側に明示的にdoneを見せるのに対して、後者は、

  // action method drain
  assign RDY_drain =
	     fsm_abort_whas_AND_fsm_abort_wget_OR_fsm_state_ETC___d71 &&
	     !fsm_start_reg ;

となり、呼び出し側をBSVの自動バックプレッシャのRDYで待たせるため、全く同じ回路となります。


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

GameFSMの改良 (25)

posted by sakurai on August 6, 2026 #1100

次に原作にもある「化石」という症状をインプリしてもらいました。

化石の動画

仕様: 化石の再現 隊列がシールド帯まで降下した状態で、左端での下降パス中に自弾が右端シールドに命中すると、これを個体命中と誤認する。シールド内の弾の位置に敵爆発音と敵爆発マークを発生させ、マーク消去時にシールドへ 16×8 の穴を開ける一方、実際に死ぬのは弾の真上とは別の列の最前列個体である。その個体の画像は消去されないまま画面に残り、以後は敵として撃てず、隊列だけが先へ進む。

発症条件の幾何定数 — 隊列の降下判定しきい値と、右端シールドの x 範囲。

`ifdef FOSSIL_BUG
`define FOSSIL_BUG_Y 176   // 原作バグ発症条件: 隊列がシールド上端(192)の1ピッチ以内                            
`define FOSSIL_BUG_X 182   // これ以右のシールド命中で発症(=右端シールド全体、x=182〜204)                       
`endif

爆発マークの描画・消去位置を保持する — 犠牲個体の座標から切り離すことで「マークと犠牲が別の場所」という原作バグの本質を表現可能にする。

`ifdef FOSSIL_BUG
   Reg#(UInt#(8)) expl_x <- mkRegU,   // インベーダ爆発マークの描画・消去位置                                   
                  expl_y <- mkRegU;   // (バグ再現時は個体位置とマーク位置が食い違う)                           
`endif

常キル時にマーク位置=個体位置を expl_x/expl_y へラッチする(挙動は従来と同一の中立改造)。

`ifdef FOSSIL_BUG
               eraseArea(
               expl_x,
               expl_y,
               16, 8);
`else
               eraseArea(
               inv_x[gx][gy],
               inv_y[gx][gy],
               16, 8);
`endif

マーク消去を個体座標ではなく expl_x/expl_y に対して行う(バグ時はシールド内を消して穴を開ける)。

`ifdef FOSSIL_BUG
      return (seq
         action
            alien_timer <= 1;
            expl_x <= inv_x[gx][gy];   // マーク位置=個体位置(通常キル)                                         
            expl_y <= inv_y[gx][gy];
         endaction
         copyArea(96, colorAtY(inv_y[gx][gy])*16+16,
                  inv_x[gx][gy], inv_y[gx][gy],
                  16, 8);
      endseq);
`else
      return (seq
         alien_timer <= 1;
         copyArea(96, colorAtY(inv_y[gx][gy])*16+16,
                  inv_x[gx][gy], inv_y[gx][gy],
                  16, 8);
      endseq);
`endif

発症4条件成立時のシールド命中を誤認キル化する本体 — マークと音は弾の位置、死ぬのは左端側の生存列の最前列、絵は残って化石になる。

`ifdef FOSSIL_BUG
               endseq else if (fbase && ymax >= `FOSSIL_BUG_Y
                               && inv_move == MoveLeftDown
                               && bullet_x >= `FOSSIL_BUG_X) seq
                  // ---- 原作バグの再現(希少条件版) ----                                                       
                  // 右→左で来て左端で下降中(=MoveLeftDown、隊列スキュー中)、                                  
                  // かつ隊列がシールド帯まで降りた状態で右端シールドを撃つと、                                 
                  // 原作の座標→個体逆算の破綻(左下原点からの列計算オーバー                                    
                  // フローが隣行へ回り込む)を模して、左端の生存個体が誤って                                    
                  // 殺される。マークと消去は弾の位置、犠牲の絵は左端に無傷で                                   
                  // 残り、隊列に取り残される(化石)。                                                           
                  fhit_alien <= False;
                  for (gx <= 0; gx < `InvCols; gx <= gx + 1) seq
                     if (inv_s[gx][inv_lead_y[gx]]) seq   // 左端の生存列を探す                                 
                        fhit_alien <= True;
                        break;
                     endseq
                  endseq
                  if (fhit_alien) seq
                     gy <= inv_lead_y[gx];
                     erasePlayerBullet(bullet_x, bullet_y);
                     inv_s[gx][gy] <= False;
                     inv_no <= inv_no - 1;
                     updateColumnFrontY();
                     playSound(SND_ALIEN_EXPLODE);
                     action                        // マークは弾の位置に描く                                    
                        alien_timer <= 1;
                        expl_x <= bullet_x - 8;
                        expl_y <= bullet_y;
                     endaction
                     copyArea(96, colorAtY(bullet_y)*16+16,
                              bullet_x - 8, bullet_y, 16, 8);
                     action
                        score <= score + inv_score[gy];
                        bscore <= bscore + inv_score[gy];
                     endaction
                  endseq else seq
                     erasePlayerBullet(bullet_x, bullet_y);   // 生存列なし: 通常のシールド命中                 
                     explodePlayerBullet(35, bullet_x, bullet_y);
                     bullet_timer <= 1;
                  endseq
`endif

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

GameFSMの改良 (24)

posted by sakurai on August 3, 2026 #1099

最近は推論能力が高いため、もっぱらClaude Fable 5を使用しています。さてCaludeに以下の仕様を実装してもらいました。

「自弾がインベーダに命中すると、爆発マークの表示期間(ALIEN_EXPL_TMAX tick)のあいだ隊列全体の移動を停止する。停止中も自機・自弾・敵弾・UFO・サウンドの処理は継続し、爆発マークの消去とともに、停止した個体から隊列の移動を再開する。」

これはオリジナル動作を観察して発見した仕様です。理由は不明ながら、爆発マーク表示中も隊列を動かすと、インベーダの動きにより爆発マークが欠ける場合があり、その干渉を防止するためと考えられます。

            for (noy <= 0; noy < `InvRows; noy <= noy + 1) seq
               // 爆発凍結中はカーソルを進めない(増分が no-op になる)
               for (nox <= 0; nox < `InvCols;
                    nox <= (alien_timer == 0) ? nox + 1 : nox) seq
                  if (inv_s[nox][noy] || alien_timer != 0) seq
                     if (alien_timer == 0)
                        updateAliens();
                     else
                        eraseAlienExplosion();
                     updatePlayer();
                     ...(以下、元のまま)...
                  endseq  // if inv
               endseq // for nox
            endseq // for noy

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

posted by sakurai on December 26, 2025 #1053

Hardware Description Language Advent Calendar 2025の第4弾の記事です。タイトルは「キラキラ星アゲイン」です。 本ブログで説明した記事その他をまとめたものとなります。詳細は本ブログにて。

図%%.1
図1053.1 HDL Advent Calendar 2025 25日目

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


ページ: