1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SystemVerilog の $sformatf を理解する

Last updated at Posted at 2025-06-25

はじめに

SystemVerilogを用いた設計や検証業務において、シミュレーション中の状態をログに出力したり、デバッグメッセージを生成したりする場面は頻繁に発生する。その際、C言語の sprintf のように、変数の値を柔軟にフォーマットして文字列化したい、と感じることが多々ある。

SystemVerilogにはその要求に応える強力なシステム関数 $sformatf が用意されている。この関数は非常に便利である一方、筆者自身、%p 指定子の使いどころや定数式での使用制限といった細かな仕様を忘れがちで、時折エラーに直面することがあった。また現在 Claude Code で作成中の mrun(仮) というSystemVerilogシミュレータ においても、$sformatf への対応が難しく、現時点で課題になっている。

そこで本記事では、自分自身の知識を再整理し、$sformatfへの理解を深めることを目的に、その機能と使い方を体系的にまとめることにした。基本的な構文から、関連するシステムタスクとの比較、実務で遭遇しやすい問題点、そしてUVMと連携した応用例までを網羅的に解説する。

この記事が、筆者と同じように $sformatf の使い方に迷うことがあるエンジニアにとって、リファレンスとして役立つものとなれば幸いである。

対象読者

  • SystemVerilogを学習中のエンジニア
  • Verilogの経験はあるが、SystemVerilogの便利な機能を十分に活用できていないエンジニア
  • UVM環境で、より可読性の高いレポートメッセージを作成したい検証エンジニア
  • デバッグ効率の向上を目指す全てのハードウェア開発者

$sformatf とは何か

$sformatf は、指定したフォーマットに従って文字列を生成し、その結果を string 型の戻り値として返すSystemVerilogの標準システム関数である。その仕様は IEEE Std 1800-2023, § 21.3.3 “Formatting data to a string” で定義されている。

これはC言語における sprintf 関数と機能的に同等であり、同様の感覚で使用することができる。

  • 入力: 第1引数にフォーマット文字列、第2引数以降にそのフォーマットに埋め込みたい値を可変長引数として渡す。
  • 出力: フォーマットされた結果が string 型の変数として返される(値渡し)。
  • 位置づけ: Verilog-2001で導入された $sformat タスクも標準規格として残存するが、これは戻り値を返さない。文字列を関数の戻り値として直接受け取れる $sformatf は、より柔軟性が高く、現代のSystemVerilogプログラミングにおいてはこちらの使用が主流である。

基本構文とフォーマット指定子

$sformatf の基本的な使い方を構文と共に示す。

string formatted_string;
formatted_string = $sformatf("フォーマット文字列", value1, value2, ...);

具体例

以下のコードは、$sformatf の具体的な使用例である。

`timescale 1ns/1ps

module tb_sformatf;
  initial begin
    int a = 13, b = 29;
    string msg;

    // 基本的な数値のフォーマット
    msg = $sformatf("%0d + %0d = %0d", a, b, a + b);
    $display(msg); // 出力: "13 + 29 = 42"

    // 16進数やゼロ埋め桁数指定
    msg = $sformatf("Address: 0x%08h, Data: 0x%02h", 32'hDEADBEEF, 8'hA5);
    $display(msg); // 出力: "Address: 0xdeadbeef, Data: 0xa5"

    // シミュレーション時間 (%t) と階層名 (%m) の利用
    #123.456; // 123.456ns 待機
    msg = $sformatf("[%0t] %m: Simulation Done.", $time);
    $display(msg); // 出力: "[123] tb_sformatf: Simulation Done."
    // 注: %tの出力は`timescale`の時間単位(time_unit)に丸められる。
    // `timescale 1ps/1ps` であれば、同じ待機時間で出力は "[123456]" となる。
  end
endmodule

主なフォーマット指定子

$display$write で使用可能な指定子の多くが $sformatf でも利用できる。

指定子 説明 例 (val = 10, str = "hello")
%d 10進数 (Decimal) 10
%h 16進数 (Hexadecimal) a
%b 2進数 (Binary) 1010
%o 8進数 (Octal) 12
%s 文字列 (String) "hello"
%f 実数 (Floating point) 10.000000
%e 指数形式の実数 (Exponential) 1.000000e+01
%t シミュレーション時間 (Time) $time の値
%m 呼び出し元の階層パス (Module path) top.sub.instance のような階層名
%p 集約型データ (Unpacked Array, Queue, Structなど) '{1, 2, 3} のような形式
%% % という文字そのものを表示 %

補足: 桁数指定と修飾子

  • %5d: 全体で5桁の幅を確保し、右寄せで表示。
  • %-5d: 全体で5桁の幅を確保し、左寄せで表示。
  • %*d: 引数で指定された幅を確保し、右寄せで表示。$sformatf("%*d", 5, 10)%5d と同等。
  • %05d: 全体で5桁の幅を確保し、空いた桁を0で埋めて右寄せで表示。

実務上の注意点と規格上の制約

$sformatf は便利だが、規格上の制約やツール依存の挙動を理解せずに使うと、予期せぬエラーや移植性の低いコードを生む原因となる。

1. 定数式 (parameter など) 内では使用不可

$sformatf は実行時に関数として評価されるため、コンパイル時に値が確定している必要がある定数式 (constant expression) の中では使用できない。 この制約は、IEEE Std 1800-2023, § 11.8 で定義されている式評価ルールに起因する。

// 不正な例: コンパイルエラーとなる
parameter ERR_MSG = $sformatf("Error on port %0d", 5);

このコードは「$sformatf is not a constant function」といったエラーを引き起こす。

2. %p の出力形式はツールに依存する

%p は集約型データを手軽に可視化できる強力な機能だが、その出力形式はIEEE規格で厳密に定められていない。そのため、シミュレータによって細部が異なる場合がある。

主要シミュレータでの出力例 (int q[$] = '{1,2,3};)

  • Questa/ModelSim, VCS, Xcelium: '{1, 2, 3}
  • Verilator: {1, 2, 3} (シングルクオートがない)

人間が読むログとしては問題ないが、生成した文字列をパーサーなどで機械処理する場合にはこの非互換性が問題となる。移植性を最優先するなら、ループを用いて自前で配列を展開し、一貫性のあるフォーマットの文字列を生成する方が安全である。

3. 生成される文字列長の上限

SystemVerilogの string 型は動的に長さが可変だが、EDAツールの実装によっては、一度に生成可能な文字列の長さに実用的な上限が設定されている場合がある。この上限はツールやバージョンによって大きく異なり、公式ドキュメントで明示されていないことも多い。例えば、過去のツールバージョンでは数MBの制限が見られた例もあるが、この値は信頼できる保証ではない。

回避策:
巨大なデータを単一の $sformatf 呼び出しで文字列化するのは避け、データを分割してループ処理で段階的に文字列を生成・追記することを検討すべきである。


推奨される使い方と応用テクニック

ここでは、$sformatf をより効果的に活用するためのベストプラクティスと応用例を紹介する。

1. 定数的なメッセージの生成方法

前述の通り、$sformatf は定数式では使えない。したがって、定数的なメッセージを生成するには、実行時に評価される functioninitial ブロックを用いる。

// 適切な例: automatic function内で生成する
// automaticを明記することで、実行時評価の意図を明確にする
function automatic string get_err_msg(int port_id);
  return $sformatf("Error on port %0d", port_id);
endfunction

// 適切な例: 変数として初期化する
string err_msg;
initial begin
  err_msg = $sformatf("Error on port %0d", 5);
end

2. パラメータに応じた動的なフォーマット

ADDR_WIDTHのようなパラメータに応じて出力の桁数を動的に変更したい場合、フォーマット指定子の * 修飾子が非常に有効である。

parameter ADDR_WIDTH = 32;
logic [ADDR_WIDTH-1:0] reg_addr = 32'h1000;
logic [7:0] reg_data = 8'hAB;
string msg;

// %0*h: `0`はゼロ埋めフラグ、`*`は幅を引数で与える指示子
// 最初の引数が桁数($ADDR_WIDTH/4$)、次の引数が値(reg_addr)
msg = $sformatf("Address: 0x%0*h, Data: 0x%h", ADDR_WIDTH/4, reg_addr, reg_data);

// $ADDR_WIDTH/4$ は 8 なので、"Address: 0x00001000, Data: ab" となる
$display(msg);

3. UVMとの連携

UVMのレポートマクロ (uvm_info, uvm_error など) の第2引数は string 型である。ここに複数の変数を含む複雑なメッセージを渡す際、$sformatf は不可欠なツールとなる。

// UVMでは、uvm_objectのフィールド名などを取得して表示することが多い。
// 以下はuvm_reg_fieldの`field`変数と、その値`val`を想定した、より実用的な例である。
// `uvm_info("CFG", $sformatf("Set %s = %0d", field.get_name(), val), UVM_LOW)

// 上記をシミュレート可能な自己完結した例として示す。
int val = 123;
`uvm_info("CFG", $sformatf("Set some_field = %0d", val), UVM_LOW)

uvm_info マクロは引数の数が固定されているため、複数の情報を一つの文字列に集約できる $sformatf は、メッセージ生成において理想的な解決策である。

4. ファイル出力との組み合わせ

シミュレーション結果をCSVなどの構造化テキストファイルとして出力する際にも $sformatf は有用である。ファイルへの書き込みには、標準関数である $fwrite$fdisplay を用いる。

int csv_fd;
initial begin
  int a, b;
  csv_fd = $fopen("result.csv", "w");
  $fdisplay(csv_fd, "time,value_a,value_b"); // ヘッダ行の書き込み

  for (int i = 0; i < 10; i++) begin
    string line;
    a = i * 2;
    b = i * 3;
    // 1. $sformatfで1行分の文字列を生成 (改行は含めない)
    line = $sformatf("%0t,%0d,%0d", $time, a, b);
    // 2. $fdisplayが自動で改行を追加して書き込む
    $fdisplay(csv_fd, "%s", line);
    #10;
  end

  $fclose(csv_fd);
end

このアプローチは、文字列生成ロジックとI/O処理を分離できるため、コードの責務が明確になる。


まとめ

本記事で解説した $sformatf の重要なポイントを以下に要約する。

  • $sformatf は、フォーマット済み文字列を戻り値として返すための主要な標準システム関数である。
  • C言語の sprintf と同様の直感的な使用感で、デバッグ、ログ出力、UVMレポートメッセージ、ファイルI/Oなど、広範な用途でその価値を発揮する。
  • 実務においては、集約型データには %p を使用すること、定数式内では使用できないという規格上の制約、そして %p の出力形式がツールに依存することを常に意識することが重要である。
  • $display や旧式の $sformat との機能的な差異を正しく理解し、目的に応じて $sformatf を活用することで、コードの可読性、再利用性、保守性を大幅に向上させることができる。

$sformatf を効果的に使いこなすことは、煩雑な文字列処理からエンジニアを解放し、より本質的な設計・検証作業への集中を可能にする。本記事が、その一助となれば幸いである。


参考文献

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?