Posts Issued in August, 2026

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で待たせるため、全く同じ回路となります。


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

posted by sakurai on August 15, 2026 #1105

修理が完成した製品の動作の状況を示します。これはモード1の、青LEDがフェードアウトしていき、赤LEDがフェードインするモードです。白は元々、LED自体にチラツキ回路が入っているようです。

図1105.1
図1105.1 完成写真


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

posted by sakurai on August 11, 2026 #1104

Claudeに外部仕様を書いてもらったものを以下に示します。

NY_night LED コントローラ 外部仕様

PIC12F1501 による 2ch LED 調光基板(SMD / JLCPCB PCBA 版)


1. 概要

3Dペーパー製ライトボックスの LED モジュール 2 系統を、PWM で独立に調光する。 モードはタクトスイッチ 1 個で巡回切替。消灯時はマイコンが SLEEP に入る。

項目 仕様
制御 IC PIC12F1501(内蔵 4MHz)
出力 ch 数 2(L / R)
出力形式 N-ch MOSFET ローサイドスイッチ
PWM 周波数 15.6 kHz
調光分解能 8 bit(0〜255)
操作系 モーメンタリスイッチ ×1

2. 電源仕様

項目
動作電圧 3.0 〜 5.5 V
想定電源 単4 ×3本(4.5V 公称)/ USB・AC アダプタ 5V
動作電流(基板のみ) 約 1 mA(LED 電流を除く)
待機電流(SLEEP) µA オーダー
書込み時の最低電圧 2.7 V
  • 電源電圧が変わっても LED の明るさ以外の特性は変化しない。MOSFET のオン抵抗は Vgs = 2.5V でも 48 mΩ で、100 mA 通電時の電圧降下は 5 mV。
  • 電池と外部アダプタを同時に接続しないこと。 アダプタから電池へ逆流する。電源切替は基板外で行う(スイッチ付き DC ジャック等)。
  • 電圧低下時はブラウンアウトリセットで安全停止する(動作中のみ有効、SLEEP 中は無効)。

3. 外部接続

基板右辺に φ1.0 mm スルーホールのワイヤパッドが 6 個、3.81 mm 等ピッチで 1 列に並ぶ。

位置 名称 内容
上から 1 VDD 電源入力 +
上から 2 GND 電源入力 −
上から 3 L_N 左 ch LED カソード側(MOSFET ドレイン)
上から 4 L_P 左 ch LED アノード側(VDD に内部接続)
上から 5 R_N 右 ch LED カソード側(MOSFET ドレイン)
上から 6 R_P 右 ch LED アノード側(VDD に内部接続)

電源ペアが上、LED 2 系統が下という並び。各ペアが隣接しているので 2 本組で配線できる。

LED 出力の条件

  • LED モジュールは X_P と X_N の間に接続する。
  • 基板側に電流制限素子は無い。 LED モジュール内部に抵抗が含まれていること。含まれない裸の LED を直結すると破損する。
  • 1 ch あたりの最大電流は 1 A(配線幅 0.4 mm / 1oz 銅による制限)。MOSFET 自体の定格は 5.7 A。
  • ローサイドスイッチのため、LED のアノード側は常に VDD 電位。カソード側が接地・開放される。

4. ユーザ操作

基板裏面に実装されたロングステムのタクトスイッチ 1 個。PCB を台紙に取り付け、台紙を貫通させて裏側から押す。

モード遷移

ボタンを離した瞬間に次のモードへ進む。

電源投入 → [1] 交互ゆらぎ
   押す  → [2] 全点灯
   押す  → [3] 交互点滅
   押す  → [0] 消灯(SLEEP)
   押す  → [1] 交互ゆらぎ  …以下巡回
モード 動作
1 交互ゆらぎ L と R が逆位相の台形波でフェード。2 秒ランプ + 両端 600 ms ホールド、全周期 約 5.2 秒。デューティ範囲 5〜250 / 255
2 全点灯 両 ch フルオン
3 交互点滅 L と R が 300 ms ごとに交互に点灯(片方点灯中はもう片方消灯)
0 消灯 両 ch オフ、マイコン SLEEP
  • 電源投入時はモード 1 から始まる。
  • ボタン応答は全モードで 10 ms 以内。チャタリング除去は 30 ms。
  • SLEEP からの復帰は短いタップでも確実に反応する。復帰時は必ずモード 1 に入る。

5. 書込みインタフェース

基板上辺の 1×6、2.54 mm ピッチのスルーホール(JP1)に、ピンヘッダを直立で常設実装する。

ピン 信号
1 MCLR / VPP
2 VDD
3 VSS
4 ICSPDAT (RA0)
5 ICSPCLK (RA1)
6 N.C.
  • MCLRE = OFF かつ LVP = OFF のため、高電圧プログラミング(VPP 約 9V)に対応した書込み器が必要(PICkit 4 で実績あり)。
  • 書込みは基板側電源(電池等)を接続したまま、書込み器のターゲット給電を OFF にして行う。LED モジュール接続状態でツール給電すると過負荷になる。JP1 の 2 番ピンは電圧検出に使うため接続したままにする。
  • SLEEP 中でも書込み可能(VPP 印加でリセットがかかる)。

6. 機構仕様

項目
基板外形 23.7 × 24.7 mm
層構成 2 層(表: VDD ベタ + 信号 / 裏: GND ベタ)
板厚 1.6 mm
表面の最大部品高さ 2.5 mm(C2、1210 MLCC)。ほかに JP1 ピンヘッダ(約 9 mm)が立つ
裏面 タクトスイッチのみ(ロングステム)
取付穴 無し
  • 固定は両面テープ。PCB 裏面を台紙表面に接着する(取付穴は使わない設計)。
  • 台紙には SW1 本体が入る貫通穴(6×6 mm より大きめ)を開け、SW1 本体を穴に沈めて PCB 面で接着する。SW1 上面だけで接着すると不安定になるため不可。
  • SW1 の選定条件: 全高(本体 + ステム)が「両面テープ厚 + 台紙厚 + 押下ストローク 0.25 mm」を超えること。すなわち ステム長 ≥ 両面テープ厚 + 台紙厚 + 0.25 mm − 本体高さ。
  • 表面の周囲 1.0 mm 以上は銅箔なし。

7. 組立順序

  1. JLCPCB より基板到着(表面 SMD 10 点実装済み)
  2. JP1 にピンヘッダをはんだ付け(常設)
  3. ICSP でファーム書込み
  4. 最後に SW1 を裏面にはんだ付け

SW1 を先に付けるとステムが干渉して基板が平らに置けず、以降の裏面作業ができなくなる。


8. 制約事項

  • 基板・LED モジュールとも電流制限素子を持たない(実測: 5V・デューティ 255 で 240 mA、上昇傾向)。このためファームウェアが全モード共通のデューティ上限 DUTY_LIMIT(既定 150/255、平均約 140 mA)で制限している。上限値はソース冒頭の #define で変更可。
  • 逆接続保護が無い。VDD / GND を逆に繋ぐと破損する。
  • 電池とアダプタの同時接続は不可。
  • モード状態は電源断で失われる。再投入時は必ずモード 1。

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

posted by sakurai on August 10, 2026 #1103

JLCPCBのGerber Viewerによる図です。PCBを5枚だけでなく、PCBAも2枚依頼したので、以下は部品も実装した図となっています。

図1103.1
図1103.1 Gerber Viewer

製造はいつものようにJLCPCBに依頼し、待ちが0.8日間、PCBは0.8日間(5枚で2 USD)、PCBAは1.1日間(2枚で21.6 USD)、配送は2.9日(8.92 USD)でした。 最終的に8月4日夜に発注して、8月10日にPCBを香港からの航空便で受け取れたので、オーダーから5.7日(32.52 USD)という感じでした。23.7 × 24.7 mmのPCBです。

図1103.2
図1103.2 完成PCB

図1103.3に、2ch(L-ch/R-ch)がモード1で交互にPWM動作をしている図を示します。光の強さが交互に増減するモードです。

図1103.3
図1103.3 PWM動作


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


ページ: