|
24 |
BSVによるUARTの再設計 (7) |
Posts Issued in August, 2026
|
23 |
BSVによるUARTの再設計 (6) |
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
|
22 |
BSVによるUARTの再設計 (5) |
次に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として自動起動にすることも可能です。
|
21 |
BSVによるUARTの再設計 (4) |
|
20 |
BSVによるUARTの再設計 (3) |
次に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 *~
|
19 |
BSVによるUARTの再設計 (2) |
インタフェースを変えたため、呼び出し元のコードで書けば、元が
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を発火論理に組み込むという違いとなります。
|
18 |
BSVによるUARTの再設計 |
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で待たせるため、全く同じ回路となります。
|
15 |
PICによるLEDドライバボードの設計 (8) |
|
11 |
PICによるLEDドライバボードの設計 (7) |
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. 組立順序
- JLCPCB より基板到着(表面 SMD 10 点実装済み)
- JP1 にピンヘッダをはんだ付け(常設)
- ICSP でファーム書込み
- 最後に SW1 を裏面にはんだ付け
SW1 を先に付けるとステムが干渉して基板が平らに置けず、以降の裏面作業ができなくなる。
8. 制約事項
- 基板・LED モジュールとも電流制限素子を持たない(実測: 5V・デューティ 255 で 240 mA、上昇傾向)。このためファームウェアが全モード共通のデューティ上限 DUTY_LIMIT(既定 150/255、平均約 140 mA)で制限している。上限値はソース冒頭の #define で変更可。
- 逆接続保護が無い。VDD / GND を逆に繋ぐと破損する。
- 電池とアダプタの同時接続は不可。
- モード状態は電源断で失われる。再投入時は必ずモード 1。
|
10 |
PICによるLEDドライバボードの設計 (5) |
JLCPCBのGerber Viewerによる図です。PCBを5枚だけでなく、PCBAも2枚依頼したので、以下は部品も実装した図となっています。

図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 完成PCB
図1103.3に、2ch(L-ch/R-ch)がモード1で交互にPWM動作をしている図を示します。光の強さが交互に増減するモードです。

図1103.3 PWM動作
ページ:






前のブログ
