SystemVerilog フォーマッタ verible のオプションの解説
Qiita HDL (SystemVerilog/Verilog/VHDL/Chisel/etc.) アドベントカレンダー 2021 9日目の記事です。
アドベントカレンダー初参加です。 久々にブログを書きました。
veribleをSystemVerilogのフォーマッタとして使ってみる
コードを一人で書いているときですらフォーマットが崩れますが、複数人で作成していると荒れます。 とくに、ハードウェア記述言語 (HDL) はフリーフォーマットが許されており、記法が言語仕様やコミュニティで厳密に定められている訳ではありません。1 したがって、複数人で開発するときはコーディングルールが必要になります。 ここで、コーディングルールの中でもフォーマットルールは白黒のつけづらい上に人間が管理するにはコスト高すぎます。 他の言語ではフォーマッタが開発されており 2 、コードの変更前にフォーマッタを利用するのがルールとなっているところも多いでしょう。
この記事ではSystemVerilogパーサである verible に含まれるフォーマッタ機能について解説します。 なるべく例示を多めに進めていく事とし、読者が手元で試しやすいようにするのが目的です。
なお、この記事は作成中の最新版であるv0.0-1761-gc8f52628をベースに記述しています。 veribleはまだまだ開発途中のツールですので、仕様が変わる可能性は高いことを念頭にご利用ください。
オプションの解説
basic format style
READMEから抜粋すると基本的な設定は以下を変更できるようです。
Flags from common/formatting/basic_format_style_init.cc: --column_limit (Target line length limit to stay under when formatting.); default: 100; --indentation_spaces (Each indentation level adds this many spaces.); default: 2; --line_break_penalty (Penalty added to solution for each introduced line break.); default: 2; --over_column_limit_penalty (For penalty minimization, this represents the baseline penalty value of exceeding the column limit. Additional penalty of 1 is incurred for each character over this limit); default: 100; --wrap_spaces (Each wrap level adds this many spaces. This applies when the first element after an open-group section is wrapped. Otherwise, the indentation level is set to the column position of the open-group operator.); default: 4;
本記事では--line_break_penalty
と--over_column_limit_penalty
以外を解説します。
--column_limit
1行分の文字数の上限を決定できます。 越えた場合は自動で折り返されますが、式はそのままです。
たとえば、デフォルトの状態で実行したとき
task automatic task_foo(logic arg1, logic [7:0] arg2 = 8'hAA, output logic arg3 = 1'b1, output logic [7:0] arg4, ref logic arg5, ref logic [15:0] arg6); $display("print some string"); $display("print %s %s %s", "some long string", "another long string", "more and more longer string"); endtask
となる場合、 --column_limit=80
とすると、
task automatic task_foo( logic arg1, logic [7:0] arg2 = 8'hAA, output logic arg3 = 1'b1, output logic [7:0] arg4, ref logic arg5, ref logic [15:0] arg6); $display("print some string"); $display("print %s %s %s", "some long string", "another long string", "more and more longer string"); endtask
となります。
--indentation_spaces
インデントの空白の個数を決めます。 デフォルトで2つです。
always_ff @(posedge (clk) or negedge (rst_n)) begin if (some_bool) begin if (another_bool) begin if (one_more_bool) begin out_0 = local_0; end end end end endmodule
のように、module
からendmodule
を起点としてインデントがスペースで挿入されます。
--indentation_spaces=4
とすると、
always_ff @(posedge (clk) or negedge (rst_n)) begin if (some_bool) begin if (another_bool) begin if (one_more_bool) begin out_0 = local_0; end end end end endmodule
のようになります3。
--wrap_spaces
括弧などでラップされたときに加えるスペースの個数です。 一番分かりやすいポートマップで比較すると、デフォルトの4のときは、
module foo #( parameter int PARAM0 = 10, parameter PARAM1 = 5, parameter string PARAMSTR = "string" ) ( input logic [7:0] in_0, input logic in_1, output logic [7:0] out_0, output logic out_1 );
となり、--wrap_spaces=8
のときは
module foo #( parameter int PARAM0 = 10, parameter PARAM1 = 5, parameter string PARAMSTR = "string" ) ( input logic [7:0] in_0, input logic in_1, output logic [7:0] out_0, output logic out_1 );
となります。
format style init
もう少し細やかな設定は以下のオプションで指定できます。
Flags from verilog/formatting/format_style_init.cc: --assignment_statement_alignment (Format various assignments: {align,flush-left,preserve,infer}); default: infer; --case_items_alignment (Format case items: {align,flush-left,preserve,infer}); default: infer; --class_member_variable_alignment (Format class member variables: {align,flush-left,preserve,infer}); default: infer; --compact_indexing_and_selections (Use compact binary expressions inside indexing / bit selection operators); default: true; --distribution_items_alignment (Aligh distribution items: {align,flush-left,preserve,infer}); default: infer; --enum_assignment_statement_alignment (Format assignments with enums: {align,flush-left,preserve,infer}); default: infer; --expand_coverpoints (If true, always expand coverpoints.); default: false; --formal_parameters_alignment (Format formal parameters: {align,flush-left,preserve,infer}); default: infer; --formal_parameters_indentation (Indent formal parameters: {indent,wrap}); default: wrap; --module_net_variable_alignment (Format net/variable declarations: {align,flush-left,preserve,infer}); default: infer; --named_parameter_alignment (Format named actual parameters: {align,flush-left,preserve,infer}); default: infer; --named_parameter_indentation (Indent named parameter assignments: {indent,wrap}); default: wrap; --named_port_alignment (Format named port connections: {align,flush-left,preserve,infer}); default: infer; --named_port_indentation (Indent named port connections: {indent,wrap}); default: wrap; --port_declarations_alignment (Format port declarations: {align,flush-left,preserve,infer}); default: infer; --port_declarations_indentation (Indent port declarations: {indent,wrap}); default: wrap; --port_declarations_right_align_packed_dimensions (If true, packed dimensions in contexts with enabled alignment are aligned to the right.); default: false; --port_declarations_right_align_unpacked_dimensions (If true, unpacked dimensions in contexts with enabled alignment are aligned to the right.); default: false; --struct_union_members_alignment (Format struct/union members: {align,flush-left,preserve,infer}); default: infer; --try_wrap_long_lines (If true, let the formatter attempt to optimize line wrapping decisions where wrapping is needed, else leave them unformatted. This is a short-term measure to reduce risk-of-harm.); default: false;
整列(alignment)の指定には4種類あり、
align
: 整える、の意味合い通りブロック内で基準となる記号に合わせて空白を挿入します。flush-left
: 左側へ詰めるように空白を1つにまとめます。preserve
: 入力のままとし変更しません。infer
: 暗示する、という意味合い通り入力を解析してalign
かflush-left
かどちらかを適用するようです。
デフォルトの状態である --assignment_statement_alignment=infer
のとき、
assign local_short = in_0; assign local_long = in_1; assign local_long_long = in_2;
となる場合、 --assignment_statement_alignment=align
とすると、
assign local_short = in_0; assign local_long = in_1; assign local_long_long = in_2;
のように等号の位置が揃うようになります。
逆に、上記のように等号の位置を揃えたとき、--assignment_statement_alignment=flush-left
を指定すると、等号前のすべての空白が1つにまとめられもう1つ前のコードブロックのようになります。
---xxx_alignment
前述の通り基準となる文字に対してどのように整列するかを指定します。
infer
だとフォーマットルールに解釈の幅が生じそうですので、複数人で作業する場合はalign
、flush-left
、preserve
のいずれかを設定した方が無難です。
--xxx_indentation
インデントの付与のルールをラップの個数かインデントの個数のどちらへ従うかを指定できます。 デフォルトではラップ(デフォルトで4つのスペース)であり、インスタンス宣言であれば、
bar #( .PARAM0 (PARAM0), .PARAM1 (PARAM1), .PARAMSTR(PARAMSTR) ) bar ( .clk(clk), .input_0(in_0), .rst_n, .out_1, .* );
のようにフォーマットされます。
一方で、--named_port_indentation=indent
を指定すると、
bar #( .PARAM0 (PARAM0), .PARAM1 (PARAM1), .PARAMSTR(PARAMSTR) ) bar ( .clk(clk), .input_0(in_0), .rst_n, .out_1, .* );
のようにポートマップのみインデントのルール(デフォルトで2つのスペース)が適用されます4。
--try_wrap_long_lines
このオプションをtrueとすると長い記述を複数行へラップします。
たとえば、デフォルトではfalseなので長い式は
always_comb begin local_0 = local_1 + local_2; if (some_bool) begin out_0 = local_1; if (anoter_bool) begin out_0 = local_1; if (last_bool) begin out_0 = local_1 + local_1 + local_1 + local_1 + local_1 + local_1 + local_1 + local_1 + local_1; end end end end
のようにそのままとなります。
--try_wrap_long_lines=true
を指定すると、
always_comb begin local_0 = local_1 + local_2; if (some_bool) begin out_0 = local_1; if (anoter_bool) begin out_0 = local_1; if (last_bool) begin out_0 = local_1 + local_1 + local_1 + local_1 + local_1 + local_1 + local_1 + local_1 + local_1; end end end end
のようにラップ(デフォルトで4つのスペース)として指定した文字数で折り返されます5。
verilog format
フォーマッタというツールとしてのオプションは以下の通りです。
Flags from verilog/tools/formatter/verilog_format.cc: --failsafe_success (If true, always exit with 0 status, even if there were input errors or internal errors. In all error conditions, the original text is always preserved. This is useful in deploying services where fail-safe behaviors should be considered a success.); default: true; --inplace (If true, overwrite the input file on successful conditions.); default: false; --lines (Specific lines to format, 1-based, comma-separated, inclusive N-M ranges, N is short for N-N. By default, left unspecified, all lines are enabled for formatting. (repeatable, cumulative)); default: ; --max_search_states (Limits the number of search states explored during line wrap optimization.); default: 100000; --show_equally_optimal_wrappings (If true, print when multiple optimal solutions are found (stderr), but continue to operate normally.); default: false; --show_inter_token_info (If true, along with show_token_partition_tree, include inter-token information such as spacing and break penalties.); default: false; --show_largest_token_partitions (If > 0, print token partitioning and then exit without formatting output.); default: 0; --show_token_partition_tree (If true, print diagnostics after token partitioning and then exit without formatting output.); default: false; --stdin_name (When using '-' to read from stdin, this gives an alternate name for diagnostic purposes. Otherwise this is ignored.); default: "<stdin>"; --verbose (Be more verbose.); default: false; --verify_convergence (If true, and not incrementally formatting with --lines, verify that re-formatting the formatted output yields no further changes, i.e. formatting is convergent.); default: true;
重要な --failsafe_success
と --inplace
について解説します。
--failsafe_success
このオプションがtrueのときは終了ステータス ($?) が常に0となります。 そのため、文法エラーでフォーマッタが失敗したときでも正常とみなされます。
もしフォーマッタがエラーとなったことを取得したい場合はfalse
を指定することとなります。
--inplace
このオプションがtrueのとき、フォーマッタが正常に実行できたときファイルを実行内容で上書きします。 git-hooksで実行するときはこのオプションをtrueにすると良いです。
付録: Dockerによる環境構築
veribleをビルドするのはbazelが必要になるため、バイナリを直接インストールする方法がもっとも簡単です。 ここではWindowsやMacでの開発、およびクラウドコンピューター上でCIへ組み込むことを想定し、Dockerを使った環境の用意の方法を紹介しておきます。
Dockerfile
FROM ubuntu:focal RUN apt-get update &&\ apt-get install -y wget curl jq &&\ rm -rf /var/lib/apt/lists/* # リリースをAPIから取得し、Ubuntu focal 用にビルドされた最新版の URL を取得してダウンロード RUN wget -qO- \ $(curl -s https://api.github.com/repos/chipsalliance/verible/releases |\ jq -r '.[0].assets[] |\ select(.name |\ test("focal-x86_64.tar.gz")) |\ .browser_download_url ') |\ tar xvz -C /tmp # 解凍されたビルド済みのバイナリを移動 RUN mv $(find /tmp -type f | grep "verible" | grep "/bin/") /usr/local/bin
使い方
上記のDockerfileをビルドし、verible:latest
とタグを付与したとして、以下のコマンドを実行します。
docker run --rm -it -v <作業ディレクトリ>:/work -w /work \ verible:latest verible-verilog-format <オプション> <対象ファイル> ...
たとえば、カレントディレクトリに対してtb/testbench.sv
とsrc/fifo.sv
に対してオプションなしでフォーマットしようとしたとき、
docker run --rm -it -v $(pwd):/work -w /work \ verible:latest verible-verilog-format tb/testbench.sv src/fifo.sv
と実行することになります。 なお、実行環境によって作業ディレクトリの指定方法は異なりますので適宜読み替えてください。
-
一応国内のコンソーシアムであるSTARCのスタイルガイドがありますが、STARCはすでに解散しているためスタイルガイドも更新されていません。もちろん、 HDLがその間に大きく進化したかといわれるとまったくそんなことはないですが。↩
-
2つで十分ですよ。↩
-
もしParameterもインデントのルールを適用したい場合は
--named_parameter_indentation=indent
とすれば可能です。↩ -
ヘルプにも記載がありますが、このオプションは「短期的な危害のリスクを減らす」とある通り、可読性は向上するものの本質的な複雑さの解消には至りませんので注意が必要です。消費リソースの削減のためにもリファクタリングが必要です。↩
Emacs Verilog-Mode でVerilog HDL をサクサク書く
モチベーション
ハードウェアの記述の大変なところは色々とありますが、テストを書きづらいところも苦労する点の1つだと思います。
そこで、Emacs Verilog-Mode を利用するとある程度の作業をスキップすることができます。 Verilog HDL を書く際の助けになれば幸いです。 SystemVerilog の記法にも対応しており、初心者から玄人まで使いやすいツールです。
以下、Verilog HDL および SystemVerilog を総称してVerilog とします。
使い方
Emacs にverilog-mode.el
を認識させてEmacs 上でVerilog を記述するのが本来の用途ですが、Emacs を使いづらい環境や使わない環境も多いため、ここはDocker を利用してフィルタ機能のみ抽出します。
手前味噌ですが、自作したDocker image であるstomoki/eda-env_emacs-verilog-mode を利用します。
以下のコマンドを実行することで利用できます。
$ docker run --rm -t -v <path-to-target-dir>:/dat \ stomoki/eda-env_emacs-verilog-mode:7 \ emacs --batch /dat/<path-to-target-file-from-target-dir> -f verilog-batch-auto
オプションごとに解説すると、
--rm : 生成したコンテナを破棄。メモリの節約。
-t : 処理した出力の表示。
-v : ボリュームのマウント。Docker 上でローカルマシンのファイルを参照できるようにする。
stomoki/... : Emacs Verilog-Mode を含むDocker image の指定
emacs ... : 実行されるコマンド
となっています。
なお、各行の末尾の \
は長いコマンドを分割して入力するためのものです。
実際に実行されるコマンドの詳細を解説すると、
--batch : バッチモードでEmacs を実行
/dat/... : Verilog-mode で処理するターゲットのファイル。`-v` で指定したディレクトリをトップとしてファイルのパスを指定。
-f : 実行するファンクション。基本的に `verilog-batch-auto` の指定で十分
となります。
例えば、ローカルマシンの/path/to/hdl
をプロジェクトのトップディレクトリとし、src/target.v
に対してVerilog-mode の処理を実行する場合、以下のようなコマンドの内容となります。
$ docker run --rm -t -v /path/to/hdl:/dat \ stomoki/eda-env_emacs-verilog-mode:7 \ emacs --batch /dat/src/target.v -f verilog-batch-auto
実際に使ってみる
実際に使ってみましょう。 Verilog で記述されたファイルを変更しながら、Emacs Verilog-mode の使い方を見ていきます。
使用する例
定番web ページ ASIC World から拝借します。
ただし、入力 data
は未使用なので削除しています。
//----------------------------------------------------- // Design Name : up_down_counter // File Name : up_down_counter.v // Function : Up down counter // Coder : Deepak Kumar Tala //----------------------------------------------------- module up_down_counter ( out , // Output of the counter up_down , // up_down control for counter clk , // clock input reset // reset input ); //----------Output Ports-------------- output [7:0] out; //------------Input Ports-------------- input [7:0] data; input up_down, clk, reset; //------------Internal Variables-------- reg [7:0] out; //-------------Code Starts Here------- always @(posedge clk) if (reset) begin // active high reset out <= 8'b0 ; end else if (up_down) begin out <= out + 1; end else begin out <= out - 1; end endmodule
このコードに対してVerilog-mode を実行しても何も起きません。
ポート生成を自動化 (AUTOARG)
では、コードを以下のように一部書き換えます。
module up_down_counter ( /*AUTOARG*/ ); //----------Output Ports-------------- output [7:0] out; //------------Input Ports-------------- input up_down, clk, reset; //------------Internal Variables-------- reg [7:0] out; //-------------Code Starts Here------- always @(posedge clk) if (reset) begin // active high reset out <= 8'b0 ; end else if (up_down) begin out <= out + 1; end else begin out <= out - 1; end endmodule
ポートリストを削除し、代わりに/*AUTOARG*/
というコメントを挿入しました。
このコメントはVerilog-mode へ特定の処理を指示するもので、Verilog-mode のキーワードはすべてAUTO
で始まります。
このAUTOARG
はコード中のディレクション指示の記述からポートリストを自動生成します。
詳細はHelp : verilog-auto-argを参照してください。
Verilog-mode の動作結果は以下の通りとなります。
module up_down_counter ( /*AUTOARG*/ // Outputs out, // Inputs up_down, clk, reset ); //----------Output Ports-------------- output [7:0] out; //------------Input Ports-------------- input up_down, clk, reset; //------------Internal Variables-------- reg [7:0] out; //-------------Code Starts Here------- always @(posedge clk) if (reset) begin // active high reset out <= 8'b0 ; end else if (up_down) begin out <= out + 1; end else begin out <= out - 1; end endmodule
このようにポートリストが自動生成されました。
これを利用する利点として、Verilog-1995 の冗長な点であったポートリストとディレクションの指定の重複をスキップできることです。 ディレクションはコード中で明示的に指定する必要があります。
もっとも、Verilog-2001 以降のANSI C 形式の記法ならばAUTOARG
は必要ないように思えます。
インスタンスのポート結線を自動化 (AUTOINST)
では、上記のテストベンチを作成してみましょう。
以下のようなテストベンチtb_up_down_counter.v
を作成しました。
`timescale 1ns/1ns module tb_up_down_counter; localparam CLK_PERIOD = 10; wire [7:0] out; reg up_down, clk, reset; up_down_counter DUT ( .out(out), .up_down(up_down), .clk(clk), .reset(reset) ); initial begin: gen_clk clk = 1'b0; forever #(CLK_PERIOD/2) clk = ~clk; end initial begin: test_count_up $monitor("[%0t] up_down %b | out %h", $time, up_down, out); up_down = 1'b0; reset = 1'b1; #(CLK_PERIOD) reset = 1'b0; up_down = 1'b1; //count-up $display("[%0t] start count-up", $time); repeat(16) @(posedge clk); $finish; end endmodule
このテストベンチを以下のようなコマンドを実行してModelSim でシミュレーションします。
$ vlib work $ vlog up_down_counter.v tb_up_down_counter.v $ vsim -c work.tb_up_down_counter -do "run -all"
以下のような出力が得られます。
# vsim -c work.tb_up_down_counter -do "run -all" # Start time: 11:38:19 on May 17,2020 # Loading work.tb_up_down_counter # Loading work.up_down_counter # run -all # [0] up_down 0 | out xx # [5] up_down 0 | out 00 # [10] start count-up # [10] up_down 1 | out 00 # [15] up_down 1 | out 01 # [25] up_down 1 | out 02 # [35] up_down 1 | out 03 # [45] up_down 1 | out 04 # [55] up_down 1 | out 05 # [65] up_down 1 | out 06 # [75] up_down 1 | out 07 # [85] up_down 1 | out 08 # [95] up_down 1 | out 09 # [105] up_down 1 | out 0a # [115] up_down 1 | out 0b # [125] up_down 1 | out 0c # [135] up_down 1 | out 0d # [145] up_down 1 | out 0e # [155] up_down 1 | out 0f # ** Note: $finish : tb_up_down_counter.v(30) # Time: 165 ns Iteration: 1 Instance: /tb_up_down_counter # End time: 11:38:20 on May 17,2020, Elapsed time: 0:00:01 # Errors: 0, Warnings: 0
ここまでは通常のシミュレーションです。 ここからVerilog-mode を利用して記述量を削減します。
まず、インスタンスを生成する部分を以下のように置換します。
`timescale 1ns/1ns module tb_up_down_counter; localparam CLK_PERIOD = 10; wire [7:0] out; reg up_down, clk, reset; up_down_counter DUT (/*AUTOINST*/); initial begin: gen_clk clk = 1'b0; forever #(CLK_PERIOD/2) clk = ~clk; end initial begin: test_count_up $monitor("[%0t] up_down %b | out %h", $time, up_down, out); up_down = 1'b0; reset = 1'b1; #(CLK_PERIOD) reset = 1'b0; up_down = 1'b1; //count-up $display("[%0t] start count-up", $time); repeat(16) @(posedge clk); $finish; end endmodule
インスタンスのポートマップをAUTOINST
に置換しました。
以下のコマンドでVerilog-mode を実行します。
なお、カレントディレクトリ/path/to/hdl
にtb_up_down_counter.v
および up_down_counter.v
があるとします。
$ docker run --rm -t -v /path/to/hdl:/dat \ stomoki/eda-env_emacs-verilog-mode:7 \ emacs --batch /dat/tb_up_down_counter.v -f verilog-batch-auto
なお、Bash 系では以下の記法も可能です。
$ docker run --rm -t -v $(pwd):/dat \ stomoki/eda-env_emacs-verilog-mode:7 \ emacs --batch /dat/tb_up_down_counter.v -f verilog-batch-auto
上記コマンドを実行したとき、tb_up_down_counter.v
の内容は以下の通りとなります。
`timescale 1ns/1ns module tb_up_down_counter; localparam CLK_PERIOD = 10; wire [7:0] out; reg up_down, clk, reset; up_down_counter DUT ( /*AUTOINST*/ // Outputs .out (out[7:0]), // Inputs .up_down (up_down), .clk (clk), .reset (reset)); initial begin: gen_clk clk = 1'b0; forever #(CLK_PERIOD/2) clk = ~clk; end initial begin: test_count_up $monitor("[%0t] up_down %b | out %h", $time, up_down, out); up_down = 1'b0; reset = 1'b1; #(CLK_PERIOD) reset = 1'b0; up_down = 1'b1; //count-up $display("[%0t] start count-up", $time); repeat(16) @(posedge clk); $finish; end endmodule
上記のように、、インスタンスの結線が自動的に生成されました。
この処理はインスタンスの参照先であるup_down_counter.v
を参照して実行されています。
src
とtb
といったように、もしデザインとテストベンチが保存されている場合は参照元のファイルの末尾に以下のようなコメントを追加してVerilog-mode へ参照先を指示する必要があります。
// Local Variables: // verilog-library-directories:("." "../src") // End:
詳細はHelp: verilog-library-directories を参照してください。
もっとも、SystemVerilog の.*
(ドットスター記述)を利用すればこの機能も不要です。
ただし、EDAツールによっては.*
を上手く解決できなかったり、.*
だと結線されている信号名が分かりづらかったりするため、この機能を使用した方が記述量を削減しつつ可読性を向上できます。
インスタンスの結線用信号の記述を自動化 (AUTOWIRE, AUTOREGINPUT)
AUTOINST
を利用することで、インスタンスのポートリストを自動化することができました。
併せて、ポートリストと接続する結線用の信号の宣言を自動化することで、さらに記述量を削減することができます。
以下のようにtb_up_down_counter.v
を書き換えます。
`timescale 1ns/1ns module tb_up_down_counter; localparam CLK_PERIOD = 10; /*AUTOWIRE*/ /*AUTOREGINPUT*/ up_down_counter DUT (/*AUTOINST*/); initial begin: gen_clk clk = 1'b0; forever #(CLK_PERIOD/2) clk = ~clk; end initial begin: test_count_up $monitor("[%0t] up_down %b | out %h", $time, up_down, out); up_down = 1'b0; reset = 1'b1; #(CLK_PERIOD) reset = 1'b0; up_down = 1'b1; //count-up $display("[%0t] start count-up", $time); repeat(16) @(posedge clk); $finish; end endmodule
このコードをVerilog-mode で変換すると、以下のようになります。
`timescale 1ns/1ns module tb_up_down_counter; localparam CLK_PERIOD = 10; /*AUTOWIRE*/ // Beginning of automatic wires (for undeclared instantiated-module outputs) wire [7:0] out; // From DUT of up_down_counter.v // End of automatics /*AUTOREGINPUT*/ // Beginning of automatic reg inputs (for undeclared instantiated-module inputs) reg clk; // To DUT of up_down_counter.v reg reset; // To DUT of up_down_counter.v reg up_down; // To DUT of up_down_counter.v // End of automatics up_down_counter DUT (/*AUTOINST*/ // Outputs .out (out[7:0]), // Inputs .up_down (up_down), .clk (clk), .reset (reset)); initial begin: gen_clk clk = 1'b0; forever #(CLK_PERIOD/2) clk = ~clk; end initial begin: test_count_up $monitor("[%0t] up_down %b | out %h", $time, up_down, out); up_down = 1'b0; reset = 1'b1; #(CLK_PERIOD) reset = 1'b0; up_down = 1'b1; //count-up $display("[%0t] start count-up", $time); repeat(16) @(posedge clk); $finish; end endmodule
AUTOWIRE
は出力ポートの、AUTOREGINPUT
は入力ポートの結線信号を自動生成しました。
前述の通り、AUTOINST
だけではSystemVerilog の.*
の互換となりますが、AUTOWIRE
およびAUTOREGINPUT
を利用すれば信号宣言なしにテストベンチのひな形を作成することができます。
さらに言えば、テスト対象回路のポートマップを知らずにテストベンチのひな形を作成することができますので、テストベンチ作成の手間を大幅に減らすことができます。
AUTOREGINPUT
はAUTOREG
の派生です。
AUTOREG
はポートマップで指定されていない出力方向のレジスタを内部信号として自動的に生成します。
デザインおよびテストベンチで信号線の宣言を忘れていないかのチェックに有用です。
階層構造の記述を自動化する (AUTOINPUT, AUTOOUTPUT)
前述した結線の自動化は複数のインスタンスが存在するときに大きな効果をもたらします。
例えば、以下のような回路を追加し、カウンタの値をSPIプロトコルのシリアル通信へ変換する、とします。
module count_to_driver ( input wire clk, input wire reset, input wire [7:0] out, output reg sck, output reg cs_n, output reg so ); // control logics are here ... endmodule
また、up_down_counter
と count_to_driver
を組み合わせ、SPIプロトコルに従ったドライバ回路を設計するとします。
このときのコードは以下のように記載できます。
module driver ( /*AUTOARG*/ ); /*AUTOINPUT*/ /*AUTOOUTPUT*/ /*AUTOWIRE*/ /*AUTOREG*/ up_down_counter counter (/*AUTOINST*/); count_to_driver cnt2spi (/*AUTOINST*/); endmodule
初出となるAUTOINPUT
および AUTOOUTPUT
は、インスタンスの入出力信号でコード上に宣言されていない信号をそのまま参照元モジュールの入出力ととして宣言します。
Verilog-mode で変換した結果は以下の通りとなります。
module driver ( /*AUTOARG*/ // Outputs so, sck, cs_n, // Inputs up_down, reset, clk ); /*AUTOINPUT*/ // Beginning of automatic inputs (from unused autoinst inputs) input clk; // To counter of up_down_counter.v, ... input reset; // To counter of up_down_counter.v, ... input up_down; // To counter of up_down_counter.v // End of automatics /*AUTOOUTPUT*/ // Beginning of automatic outputs (from unused autoinst outputs) output cs_n; // From cnt2spi of count_to_driver.v output sck; // From cnt2spi of count_to_driver.v output so; // From cnt2spi of count_to_driver.v // End of automatics /*AUTOWIRE*/ // Beginning of automatic wires (for undeclared instantiated-module outputs) wire [7:0] out; // From counter of up_down_counter.v // End of automatics /*AUTOREG*/ up_down_counter counter (/*AUTOINST*/ // Outputs .out (out[7:0]), // Inputs .up_down (up_down), .clk (clk), .reset (reset)); count_to_driver cnt2spi (/*AUTOINST*/ // Outputs .sck (sck), .cs_n (cs_n), .so (so), // Inputs .clk (clk), .reset (reset), .out (out[7:0])); endmodule
このように、すべてのインスタンスの結線が解決され、必要な入出力ポートが宣言され、ポートマップが補完されています。
clk
やreset
といった共通の入力信号も重複なく解決されています。
また、ANSI C 形式でポートマップを宣言するときは以下のように記述します。
module driver ( /*AUTOINPUT*/ /*AUTOOUTPUT*/ ); /*AUTOWIRE*/ /*AUTOREG*/ up_down_counter counter (/*AUTOINST*/); count_to_driver cnt2spi (/*AUTOINST*/); endmodule
この変換結果は以下のようになります。
module driver ( /*AUTOINPUT*/ // Beginning of automatic inputs (from unused autoinst inputs) input clk, // To counter of up_down_counter.v, ... input reset, // To counter of up_down_counter.v, ... input up_down, // To counter of up_down_counter.v // End of automatics /*AUTOOUTPUT*/ // Beginning of automatic outputs (from unused autoinst outputs) output cs_n, // From cnt2spi of count_to_driver.v output sck, // From cnt2spi of count_to_driver.v output so // From cnt2spi of count_to_driver.v // End of automatics ); /*AUTOWIRE*/ // Beginning of automatic wires (for undeclared instantiated-module outputs) wire [7:0] out; // From counter of up_down_counter.v // End of automatics /*AUTOREG*/ up_down_counter counter (/*AUTOINST*/ // Outputs .out (out[7:0]), // Inputs .up_down (up_down), .clk (clk), .reset (reset)); count_to_driver cnt2spi (/*AUTOINST*/ // Outputs .sck (sck), .cs_n (cs_n), .so (so), // Inputs .clk (clk), .reset (reset), .out (out[7:0])); endmodule
ここで、ポートリストに型宣言が含まれないことに注意すること。
SystemVerilog の場合、AUTOWIRE
の代わりにAUTOLOGIC
の使用を推奨します。
その場合、ポートリストにlogic
として型宣言が含まれるようになります。
テスト用スタブを自動生成する (AUTOINOUTMODULE)
テスト作成時に困るのが外部モジュールのふるまいの記述です。
外部モジュールのふるまいをテストコードに直接記述すると、大抵が複雑化し保守性を低下させる要因となりやすいです。 そのため、ここはモックやスタブとして外部モジュールと同様のインタフェースを持つテスト用モジュールを作成し、必要ならばロジックを増やしていく戦略をとると良いと思います。 このメリットとして、テストコードの行数を減らせることとと作成したテストモジュールを他のテストへ再利用できる点が挙げられます。
上記のdriver
のスタブを作成してみましょう。
このスタブはdriver
モジュールが完成するまで上位モジュールから参照してもコンパイルが通るようにし、driver
モジュールから返答がなくてもよい部分を先に作るために有用です。
Verilog-mode のFAQ を参考にして以下のように記述します。
module stub_driver #( /*AUTOINOUTPARAM("driver")*/ )( /*AUTOINOUTMODULE("driver")*/ ); /*AUTOWIRE*/ /*AUTOREG*/ /*AUTOTIEOFF*/ wire _unused_ok = &{1'b0, /*AUTOUNUSED*/ 1'b0}; endmodule
Verilog-mode で変換すると以下のようになります。
module stub_driver #( /*AUTOINOUTPARAM("driver")*/ )( /*AUTOINOUTMODULE("driver")*/ // Beginning of automatic in/out/inouts (from specific module) output cs_n, output sck, output so, input clk, input reset, input up_down // End of automatics ); /*AUTOWIRE*/ /*AUTOREG*/ /*AUTOTIEOFF*/ // Beginning of automatic tieoffs (for this module's unterminated outputs) wire cs_n = 1'h0; wire sck = 1'h0; wire so = 1'h0; // End of automatics wire _unused_ok = &{1'b0, /*AUTOUNUSED*/ // Beginning of automatic unused inputs clk, reset, up_down, // End of automatics 1'b0}; endmodule
AUTOINOUTPARAM
およびAUTOINOUTMODULE
は引数として指定したモジュールのポートリストを参照し、内容を複製します。
AUTOTIEOF
は未使用の出力信号を1`b0 へ固定し、不定値となることを防ぎます。
AUTOUNUSED
は未使用な入力信号を列挙します。
この_unused_ok
では全ての未使用な入力信号線をまとめ、全てのビットのANDを生成しています。
_unused_ok
は1'b0 とのANDとなっているため、常に1'b0となります。
必要ならばこの信号線をリンタなど他のツールのスコープから除外するのがベターだそうです。
一方で、driver
の開発を考えた場合、SPIの接続先が必要となります。
その場合、stub_spi
として以下のように記述します。
module stub_spi #( /*AUTOINOUTPARAM("driver")*/ ) ( /*AUTOINOUTCOMP("driver", "", "^input.*")*/ ); /*AUTOWIRE*/ /*AUTOREG*/ /*AUTOTIEOFF*/ wire _unused_ok = &{1'b0, /*AUTOUNUSED*/ 1'b0}; endmodule
Verilog-mode で変換した結果は以下の通りです。
module stub_spi #( /*AUTOINOUTPARAM("driver")*/ ) ( /*AUTOINOUTCOMP("driver", "", "^input.*")*/ // Beginning of automatic in/out/inouts (from specific module) input cs_n, input sck, input so // End of automatics ); /*AUTOWIRE*/ /*AUTOREG*/ /*AUTOTIEOFF*/ wire _unused_ok = &{1'b0, /*AUTOUNUSED*/ // Beginning of automatic unused inputs cs_n, sck, so, // End of automatics 1'b0}; endmodule
AUTOINOUTCOMP
はAUTOINOUTMODULE
の派生で、指定したモジュールのポートリストを参照して逆方向のポートリストを生成します。
ここで重要なのが2番目の引数と3番目の引数です。
2番目の引数は「正規表現による参照元のポートリストの信号名に対するフィルタリング設定」で、例えば^spi_.*
のように指定するとspi_
から始まる信号線のみをポートリストの対象とします。
3番目の引数は「正規表現による変換後のポートリストのディレクションおよびデータ型に対するフィルタリング設定」で、この例ではinput
から始まるポートのみを出力する設定となります。
そのため、AUTOINOUTCOMP("driver","","^input.*")
は、
1. driver
モジュールの、
2. すべてのポートに対して、
3. 反対のディレクションへ変換し、
4. input
から始まる全てのポートを出力する
という指示となります。
今回はSPI通信関係の信号線を抽出したかったこととSPI通信に関わる信号線のみが出力だったため上記のような条件設定となりました。
特定のポートのみ出力したい場合は信号名にspi_
のようなプレフィックスを付け、第2引数で指定するのが良いと思います。
詳細はHelp: verilog-auto-inout-module を参照してください。
上記スタブを使用することで、SPI通信関連の信号を内部で処理することができます。 スタブ中に制御ロジックやタスクを追記することでモックとして作り込むこともできます。
実際にスタブを用いたテストベンチを記述してみましょう。
driver
モジュールのテストベンチを以下のように記述します。
module tb_driver; /*AUTOREGINPUT*/ /*AUTOWIRE*/ /*AUTOREG*/ driver DUT (/*AUTOINST*/); stub_spi stub_spi(/*AUTOINST*/); // stimulus are here... endmodule
verilog-mode で変換すると以下のようになります。
module tb_driver; /*AUTOREGINPUT*/ // Beginning of automatic reg inputs (for undeclared instantiated-module inputs) reg clk; // To DUT of driver.v reg reset; // To DUT of driver.v reg up_down; // To DUT of driver.v // End of automatics /*AUTOWIRE*/ // Beginning of automatic wires (for undeclared instantiated-module outputs) wire cs_n; // From DUT of driver.v wire sck; // From DUT of driver.v wire so; // From DUT of driver.v // End of automatics /*AUTOREG*/ driver DUT (/*AUTOINST*/ // Outputs .cs_n (cs_n), .sck (sck), .so (so), // Inputs .clk (clk), .reset (reset), .up_down (up_down)); stub_spi stub_spi(/*AUTOINST*/ // Inputs .cs_n (cs_n), .sck (sck), .so (so)); // stimulus are here ... endmodule
上記の通り、スタブおよびテスト対象回路の記述量を削減しつつテストベンチを構築することができます.
まとめ
Emacs Verilog-mode を利用することでテストベンチの記述量を減らし、作成スピードを向上することができます。 どこかのモジュールで変更があった場合、信号名の書き換えがコマンドによって自動的に書き換えられるのが利点です。
よいHDL設計ライフを!
コメの仕入れ先
通っていた米屋が閉店とのこと。 どうやら、米の卸しの組合が解散するのに合わせて閉店するらしい。 米屋が流行る時代でもないというのは分かるし、米を手に入れたければドラッグストアでもスーパーマーケットでもどこでも売っている。
しかし、寂しいものだ。 どこで五分漉きの米を手に入れればよいのだろうか。 七分漉きの米を手に入れるためには、玄米で購入した米を自身で精米しなければならないのだろうか。
個人商店には厳しい時代だ...とは思いつつ、個人で商いをやっていくのはいつでも大変なのだろう。 くだんの米屋は今月の在庫がなくなり次第閉めるそうだ。 その前に購入できたのは助かった。
効率的なHDL開発環境を求めて1-1: Dockerによる仮想環境の構築
Dockerとは?
ある程度人口に膾炙した技術であるため割愛。
Dockerを使うと何が良い?
- ホストOSに依存せずにEDA環境が構築できる。
- EDAツールのバージョンを統一しやすい
- ホストOSのバージョンに依存しない
- 開発環境とテスト環境をまとめて整備
- ツールのバージョンアップもまとめてできる
- ツールのインストール時間の削減
- GUIの手間を削減
- あらかじめイメージをビルドしておけば実質1コマンドで可能かつ短時間で導入可能
- スケールアウト・インによる負荷分散
- まだ勉強途中
- 参考: Dockerコンテナをスケールアウトしてみよう
実際にやってみた
- Dockerhub URL <stomoki/eda-env_verilator_emacs-verilog-mode>
- Github URL <Ag48/eda-env_verilator_emacs-verilog-mode>
Dockerhubにビルド済みイメージがあるため、docker pull stomoki/eda-env__verilatoremacs-verilog-mode
でpullすれば使える。
ssh用アカウントとして、
- ID: eda
- PW: I1oveEDA
を用意した。
明らかな脆弱性だが、docker run
の後コンテナ内で/etc/init.d/sshd start
することでアクセスできるようになる。
使える機能
- verilator
- Lint機能が利用可能、ただしtbに対するLintは微妙
- 使い方:
verilator --lint-only <top_level.v>
- 参考: Verilator
--lint-only Check the files for lint violations only, do not create any other output. You may also want the -Wall option to enable messages that are considered stylistic and not enabled by default. If the design is not to be completely Verilated see also the --bbox-sys and --bbox-unsup options.
- emacs-verilog-mode:
- IFやインスタンス、リセット記述等の工数を削減
- 使い方:
emacs --batch <file.v> -l verilog-mode -f verilog-auto -f save-buffer --no-site-file
- 下位モジュール記述が同一フォルダにないと使用できない
- 参考: Verilog-mode
そいつはDocker、OSS達が築いていった知恵の1つだ。うまく使いこなせよ
効率的なHDL開発環境を求めて
モチベーション
- HDL開発に精を出したい
- しかしツールは無料版しか使えない!
- 開発者は複数を想定、できるだけ環境を整えたい
- Continuous Integration (CI) を実現したい
- Docker 試したい
実装内容
"Fanfare Band Highlights WMC 2017" レビュー Disc 1
WMC2017のFanfare Band Highlightが11月上旬に届いた(個人輸入なので日本最速!)のですが、なかなか聴くためのまとまった時間を取れずに過ごしてしまったので、今更ながら簡易的なレビューをしようと思います。
A Diamond for Parker (Rob Goorhuis)
本作品は"testpiece 1st division"、つまり今大会の1部の課題曲の中で最も優れた演奏だったといえます。
Thomas Tallisによる「大主教パーカーのための詩編曲」 Archbishop Parker's Metrical Psalter に触発された作品となっております。
また、同様のテーマを使用して有名な作品といえば、R.V WilliamsのFantasia on a Theme of Thomas Tallis です。
イントロで心を掴まれ、静かに運ばれる主題に心奪われます。
作品としては、厳かな始まりからあふれる勢いに乗ったトッカータ調の主題へ繋がります。 その後広がりを見せたコラール調(原曲風です)に移行し、最後はトッカータ調を再現して終わりを迎えます。 楽器の特徴を遺憾なく発揮させる作曲となっています。
Astral Poetry (Olier Waespi)
Champion Concert Divisionの演奏です。
冒頭のA.Saxのソロは絶品ですが、全体的にソロ(カデンツァ)がちりばめられており、さらに高難易度の合奏によって支えられている曲の作りとなっております。 太陽系の惑星たちがおりなす公転のダンスを、ジャジーな超絶技巧とバロック式の構成(緩急緩急)で紡ぎあげたのが本作とのこと。
パワーにあふれております。 目まぐるしく変わる主題、流れるように受け渡される各種カデンツァが、太古から続く惑星のめぐりあわせを美しく表現します。
Earthrise (Nigel Clarke, arr. Luc Vertommen)
冒頭の音響は誰にも真似できないだろう(演奏も、作曲も!)という感じで迫ってきます。 この曲はアポロ8号を題材にしており、急緩急(地球の引力からの脱出-宇宙からの地球-帰還)を題材にしているそうです。
中間部の美しさはErick SwigersのAuroraをちょっと想起させる感じです。
Eric Swiggers - Compositions for Windband
この曲は冒頭で観客の心をわしづかみにするでしょう。
A Child like You (Andy Scott、arr.Thom Zigterman)
近年必ず1作はある、合奏にナレーションおよび独唱がつく作品です。
もともとはAndy Scott氏が作成したブラスバンド用のショートオペラを、本バンドがFanfare Bandにアレンジしたそうです。
Andy Scott (saxophonist and composer) - Wikipedia
イディ・アミンが独裁政権を敷く70年代初頭のウガンダにて、17歳のウガンダとアジア(印僑:インドからの移民、だと思います)の混血児の受け入れをめぐる話となっています。 Andy氏が6歳の頃の、彼の両親によるその少年の受け入れを通した難民に関する話が曲の構成のもととなっているそうです。
イディ・アミンといえば、映画「ラストキング・オブ・スコットランド」ですね。
Breathless from Jazz (Philip Wilby)
Philip氏が作成した4編から成るシンフォニックダンス"Jazz"の最終楽章となっています。
随所にちりばめられるNew Yorkへの憧憬、または"West side story"を作曲したL. Barnstein氏からの影響を隠さずに繰り広げられる、短いながらもドラマチックな作品となっています。
fanfaria musica* 2013を振り返る
本記事は、私が関わっているFanfare Orkest バンド "fanfaria musica*" (https://fanfaria.wixsite.com/fanfariamusica)の結成5周年を迎えました。 そのため、過去に私が書いたブログ記事を引用することで、結成当時を振り返りたいと思います。 この記事はちょうど1回目の演奏会であるfanfaria musica* 2013が終了した後に書いたものです。 見出しを付ける加工は施しましたが、基本的に手直しは加えておりません。
歴史的文章としての価値があればよいかも。
個人的には十分楽しめた演奏会でした。 朝の練習から、トロンボーンが壊れてその代替楽器を求めて東奔西走したことなども含めて、楽しいものだったと思います。
これほど本番が大丈夫か気になった演奏会は類なしです。 まるでジェットコースターでした。
反省点
楽しい思い出ばかり語っていてもきりがないので、ここでは反省点を述べたいと思います。
主なものを3つ出すと、 ・練習の密度が低い ・連絡が不徹底 ・演奏会の構築が遅い が挙げられるでしょう。
まず、練習の密度について。 これは元々想定していた「人が集まらない」ことをどうやってクリアするか、に掛かってくると思います。 次回からは、誰もがどのセクションでも吹けるようにしておくことや、録音機材を用いて練習を共有できるようにすることが必要です。
次に、ホウレンソウについて。 上から一方的に連絡することは出来ますが、全員の意見を共有することが出来なかったり、出席の情報が徹底できていなかったり、練習会場や楽器の貸し出しなどの情報にも不備が多かったと思います。 次回からは、「誰がどうするか」をより明確にする必要があります。特に、練習会場と楽器はバンドの活動の土台なので、こういう場面でぬかりないようにしなければなりません。
そして、演奏会について。 ホールがだいぶ早く決定していたのは本当にありがたかったです。 しかし、ホールはもう6か月前ぐらいに決まっていたのに曲が揃わないという事態は、演奏会のクオリティへ大きな衝撃を与えたこと間違いないでしょう。 いい教訓となったと思います。曲と練習場所が揃わないと演奏会は始まらないわけですね。 今度やるならば、編曲が揃ってから本格的に始めるのがよいと思います。
他にも、フリューゲルによるサウンドづくりの件やアンサンブルレベルの低さなど、反省すべき点はいくらでもありますね。
楽器別の反省
少し詳しく楽器別に分析しましょう。
私の反省点としては、トランペットのピッチと音程が悪いことでしょう。これは練習不足としか言いようがありません。また音量過多な部分が多いのが気になります。全体のバランスを考えずに吹いている感じ。
私見
今回の演奏会はかなり軽い気持ちで引き受けました。
演奏会やるから振って!と言われて、断る理由がありませんからね。3月ならば暇ですし。ちなみに、この頃は大阪大学吹奏楽団のフェアウェルを失念していました。関係者の方々、申し訳ございません。
今回の壁は、物が揃わないことでした。 人員、楽器、譜面。全てが満足にあるわけじゃない環境でした。大学のサークル時代ではすべてが豊富にありましたからね。人がいないパートなんて存在しませんでしたし、楽器は個人持ちを含め十分な数がありました。譜面は必ず期限までに国内外から取り寄せている。今考えてみるとものすごく恵まれた環境でした。 まあ、このような事務関係が回らなければ音楽活動がままならないため、しっかりしている必要があるわけですが。 しかし、今回は甘くありませんでした。 本当に手作りな演奏会でした。私自身、自分で吹かないのにフリューゲルホルンを3本、伝手を頼って借りました。練習場所も大学の間借り。楽譜も新たに編曲が必要なものがあったり、出版社の事情により遅れたりと、もう前途多難でした。最後の楽譜が出来上がったのが本番1週間前でした。まさに危機一髪です。人が集まらないのも大打撃でした。いろいろな演奏会が重なっていて参加者が忙しい時期に重なってしまったのも原因ですが、最大で6割しか集まらない現実を突き付けられたときは、絶体絶命でした。
しかし、私はこの演奏会を成功したかったのです。 公には言っていませんが、今回私の友人が参加しています。結構深い関係な気がします。 その友人が、今回大学を卒業して完答へ社会人として引っ越すわけです。引っ越すことがどう、というわけではありません。別に会えないわけじゃないですから。 その友人が参加する最後の演奏会だろうと思いまして、余計な心遣いだというのは重々承知ですが、なんとか心に残る思い出になれば、と一計を練ったわけです。別にいい思い出になれとも悪い思い出になれとも思っていたわけではありません。なにかしら、その友人に残ればいいな、と。よく言うじゃないですか。モノより思い出って。
先ほどは主な目的ですが、こういうジョイントコンサートのようなイベントに私は疎かったので、だんだん知らない人との関わり合いが増えるのです。これがまたとても楽しくて。 本演奏会がなければ知り合えなかった人がかなりいるわけですので、本当に本演奏会に参加して良かったなぁ、と思います。
謝辞
全員揃ったのは本番以外ありませんでしたが、誰一人欠けても本演奏会のサウンドは奏でられなかったわけです。 だから出会えたことに感謝。 願わくば、私と出会った人に何か与えられたらば…と思います。
演奏会を聴きに来ていただいた方々はもちろんのこと、参加者の皆様、そして本演奏会を企画した人々、本当に感謝しています。 特に統括と会計担当者には、深謝申し上げなければなりません。ありがたやありがたや。彼女らがいなければこの3か月は回らなかった!
日本語がおかしいのは5年前だからなのか、それとも勢いで書いているためなのか。 今年の反省も書きます。