Very Hard Delight Life

内容はLinux, HW, プログラミング, HaFaBra.

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: 暗示する、という意味合い通り入力を解析して alignflush-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 だとフォーマットルールに解釈の幅が生じそうですので、複数人で作業する場合はalignflush-leftpreserveのいずれかを設定した方が無難です。

--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が必要になるため、バイナリを直接インストールする方法がもっとも簡単です。 ここではWindowsMacでの開発、およびクラウドコンピューター上で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.svsrc/fifo.svに対してオプションなしでフォーマットしようとしたとき、

docker run --rm -it -v $(pwd):/work -w /work \
    verible:latest verible-verilog-format tb/testbench.sv src/fifo.sv

と実行することになります。 なお、実行環境によって作業ディレクトリの指定方法は異なりますので適宜読み替えてください。


  1. 一応国内のコンソーシアムであるSTARCのスタイルガイドがありますが、STARCはすでに解散しているためスタイルガイドも更新されていません。もちろん、 HDLがその間に大きく進化したかといわれるとまったくそんなことはないですが。

  2. Pythonであれば black が有名ですし、Go言語であれば gofmt が開発環境に含まれています。

  3. 2つで十分ですよ。

  4. もしParameterもインデントのルールを適用したい場合は--named_parameter_indentation=indentとすれば可能です。

  5. ヘルプにも記載がありますが、このオプションは「短期的な危害のリスクを減らす」とある通り、可読性は向上するものの本質的な複雑さの解消には至りませんので注意が必要です。消費リソースの削減のためにもリファクタリングが必要です。

Emacs Verilog-Mode でVerilog HDL をサクサク書く

モチベーション

ハードウェアの記述の大変なところは色々とありますが、テストを書きづらいところも苦労する点の1つだと思います。

そこで、Emacs Verilog-Mode を利用するとある程度の作業をスキップすることができます。 Verilog HDL を書く際の助けになれば幸いです。 SystemVerilog の記法にも対応しており、初心者から玄人まで使いやすいツールです。

以下、Verilog HDL および SystemVerilog を総称してVerilog とします。

使い方

Emacsverilog-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 

(8-Bit Up-Down Counter より)

このコードに対して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/hdltb_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 を参照して実行されています。 srctb といったように、もしデザインとテストベンチが保存されている場合は参照元のファイルの末尾に以下のようなコメントを追加して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 を利用すれば信号宣言なしにテストベンチのひな形を作成することができます。 さらに言えば、テスト対象回路のポートマップを知らずにテストベンチのひな形を作成することができますので、テストベンチ作成の手間を大幅に減らすことができます。

AUTOREGINPUTAUTOREG の派生です。 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_countercount_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

このように、すべてのインスタンスの結線が解決され、必要な入出力ポートが宣言され、ポートマップが補完されています。 clkreset といった共通の入力信号も重複なく解決されています。

また、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

AUTOINOUTCOMPAUTOINOUTMODULE の派生で、指定したモジュールのポートリストを参照して逆方向のポートリストを生成します。 ここで重要なのが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コマンドで可能かつ短時間で導入可能
  • スケールアウト・インによる負荷分散

実際にやってみた

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開発に精を出したい
  • しかしツールは無料版しか使えない!
  • 開発者は複数を想定、できるだけ環境を整えたい
    • Windows しか使えない人も混ざっている!
    • 筆者はLinux で開発したい
  • Continuous Integration (CI) を実現したい
  • Docker 試したい

実装内容

  • Docker によるHDL開発環境の仮想化
    • Verilog-HDLによる実装
      • EmacsVerilog-mode を使用した配線の手間削減
      • Veridator を使用したVerilog-HDL Lint 機能
    • ModelSim によるHDLテスト環境
    • バージョン管理およびCI の実現
      • GitBucket による実現

"Fanfare Band Highlights WMC 2017" レビュー Disc 1

WMC2017のFanfare Band Highlightが11月上旬に届いた(個人輸入なので日本最速!)のですが、なかなか聴くためのまとまった時間を取れずに過ごしてしまったので、今更ながら簡易的なレビューをしようと思います。

WMC Kerkrade :: en

A Diamond for Parker (Rob Goorhuis)

本作品は"testpiece 1st division"、つまり今大会の1部の課題曲の中で最も優れた演奏だったといえます。

Thomas Tallisによる「大主教パーカーのための詩編曲」 Archbishop Parker's Metrical Psalter に触発された作品となっております。

youtu.be

また、同様のテーマを使用して有名な作品といえば、R.V WilliamsのFantasia on a Theme of Thomas Tallis です。

youtu.be

イントロで心を掴まれ、静かに運ばれる主題に心奪われます。

作品としては、厳かな始まりからあふれる勢いに乗ったトッカータ調の主題へ繋がります。 その後広がりを見せたコラール調(原曲風です)に移行し、最後はトッカータ調を再現して終わりを迎えます。 楽器の特徴を遺憾なく発揮させる作曲となっています。

Astral Poetry (Olier Waespi)

Champion Concert Divisionの演奏です。

冒頭のA.Saxのソロは絶品ですが、全体的にソロ(カデンツァ)がちりばめられており、さらに高難易度の合奏によって支えられている曲の作りとなっております。 太陽系の惑星たちがおりなす公転のダンスを、ジャジーな超絶技巧とバロック式の構成(緩急緩急)で紡ぎあげたのが本作とのこと。

パワーにあふれております。 目まぐるしく変わる主題、流れるように受け渡される各種カデンツァが、太古から続く惑星のめぐりあわせを美しく表現します。

Earthrise (Nigel Clarke, arr. Luc Vertommen)

冒頭の音響は誰にも真似できないだろう(演奏も、作曲も!)という感じで迫ってきます。 この曲はアポロ8号を題材にしており、急緩急(地球の引力からの脱出-宇宙からの地球-帰還)を題材にしているそうです。

アポロ8号 - Wikipedia

中間部の美しさはErick SwigersのAuroraをちょっと想起させる感じです。

Eric Swiggers - Compositions for Windband

この曲は冒頭で観客の心をわしづかみにするでしょう。

A Child like You (Andy Scott、arr.Thom Zigterman)

近年必ず1作はある、合奏にナレーションおよび独唱がつく作品です。

もともとはAndy Scott氏が作成したブラスバンド用のショートオペラを、本バンドがFanfare Bandにアレンジしたそうです。

youtu.be

Andy Scott (saxophonist and composer) - Wikipedia

イディ・アミンが独裁政権を敷く70年代初頭のウガンダにて、17歳のウガンダとアジア(印僑:インドからの移民、だと思います)の混血児の受け入れをめぐる話となっています。 Andy氏が6歳の頃の、彼の両親によるその少年の受け入れを通した難民に関する話が曲の構成のもととなっているそうです。

イディ・アミンといえば、映画「ラストキング・オブ・スコットランド」ですね。

youtu.be

Breathless from Jazz (Philip Wilby)

Philip氏が作成した4編から成るシンフォニックダンス"Jazz"の最終楽章となっています。

随所にちりばめられるNew Yorkへの憧憬、または"West side story"を作曲したL. Barnstein氏からの影響を隠さずに繰り広げられる、短いながらもドラマチックな作品となっています。

youtu.be

fanfaria musica* 2013を振り返る

本記事は、私が関わっているFanfare Orkest バンド "fanfaria musica*" (https://fanfaria.wixsite.com/fanfariamusica)の結成5周年を迎えました。 そのため、過去に私が書いたブログ記事を引用することで、結成当時を振り返りたいと思います。 この記事はちょうど1回目の演奏会であるfanfaria musica* 2013が終了した後に書いたものです。 見出しを付ける加工は施しましたが、基本的に手直しは加えておりません。

歴史的文章としての価値があればよいかも。


個人的には十分楽しめた演奏会でした。 朝の練習から、トロンボーンが壊れてその代替楽器を求めて東奔西走したことなども含めて、楽しいものだったと思います。

これほど本番が大丈夫か気になった演奏会は類なしです。 まるでジェットコースターでした。

反省点

楽しい思い出ばかり語っていてもきりがないので、ここでは反省点を述べたいと思います。

主なものを3つ出すと、 ・練習の密度が低い ・連絡が不徹底 ・演奏会の構築が遅い が挙げられるでしょう。

まず、練習の密度について。 これは元々想定していた「人が集まらない」ことをどうやってクリアするか、に掛かってくると思います。 次回からは、誰もがどのセクションでも吹けるようにしておくことや、録音機材を用いて練習を共有できるようにすることが必要です。

次に、ホウレンソウについて。 上から一方的に連絡することは出来ますが、全員の意見を共有することが出来なかったり、出席の情報が徹底できていなかったり、練習会場や楽器の貸し出しなどの情報にも不備が多かったと思います。 次回からは、「誰がどうするか」をより明確にする必要があります。特に、練習会場と楽器はバンドの活動の土台なので、こういう場面でぬかりないようにしなければなりません。

そして、演奏会について。 ホールがだいぶ早く決定していたのは本当にありがたかったです。 しかし、ホールはもう6か月前ぐらいに決まっていたのに曲が揃わないという事態は、演奏会のクオリティへ大きな衝撃を与えたこと間違いないでしょう。 いい教訓となったと思います。曲と練習場所が揃わないと演奏会は始まらないわけですね。 今度やるならば、編曲が揃ってから本格的に始めるのがよいと思います。

他にも、フリューゲルによるサウンドづくりの件やアンサンブルレベルの低さなど、反省すべき点はいくらでもありますね。

楽器別の反省

少し詳しく楽器別に分析しましょう。

サックス:
S.Saxがないと大幅に崩れる。A.Saxのサウンドがもう少し欲しかった。16分が整わない。メロディが平坦。
フリューゲル:
和音が組めない。One Playでなんとかクオリティを切り詰めた感じ。16分やシンコペーションを聴くと基礎が足りない印象。
ホルン:
人数の割には存在感がない。強奏と弱奏の差が分からない。
トランペット:
基本的にピッチとアーティキュレーションが不徹底。音圧がバンドとして強すぎる。他のパートが聴けていない。
トロンボーン
弱奏が不安定。和音が整わない。リズムはまだまだ練習が必要。
バリトン
もう少し浮く音質を目指すべき。4本もあるのに、存在感が欲しいところ。
ユーフォ:
旋律を吹く技術が必要。中低音としての安定感が欲しかった。
チューバ:
パートとしてのまとまりが足りない。基本的に弱奏が大きすぎる。
パーカッション:
縦が合わない。音色はもっとこだわりを。呼吸を合わせる感覚が欲しい。

私の反省点としては、トランペットのピッチと音程が悪いことでしょう。これは練習不足としか言いようがありません。また音量過多な部分が多いのが気になります。全体のバランスを考えずに吹いている感じ。

私見

今回の演奏会はかなり軽い気持ちで引き受けました。

演奏会やるから振って!と言われて、断る理由がありませんからね。3月ならば暇ですし。ちなみに、この頃は大阪大学吹奏楽団のフェアウェルを失念していました。関係者の方々、申し訳ございません。

今回の壁は、物が揃わないことでした。 人員、楽器、譜面。全てが満足にあるわけじゃない環境でした。大学のサークル時代ではすべてが豊富にありましたからね。人がいないパートなんて存在しませんでしたし、楽器は個人持ちを含め十分な数がありました。譜面は必ず期限までに国内外から取り寄せている。今考えてみるとものすごく恵まれた環境でした。 まあ、このような事務関係が回らなければ音楽活動がままならないため、しっかりしている必要があるわけですが。 しかし、今回は甘くありませんでした。 本当に手作りな演奏会でした。私自身、自分で吹かないのにフリューゲルホルンを3本、伝手を頼って借りました。練習場所も大学の間借り。楽譜も新たに編曲が必要なものがあったり、出版社の事情により遅れたりと、もう前途多難でした。最後の楽譜が出来上がったのが本番1週間前でした。まさに危機一髪です。人が集まらないのも大打撃でした。いろいろな演奏会が重なっていて参加者が忙しい時期に重なってしまったのも原因ですが、最大で6割しか集まらない現実を突き付けられたときは、絶体絶命でした。

しかし、私はこの演奏会を成功したかったのです。 公には言っていませんが、今回私の友人が参加しています。結構深い関係な気がします。 その友人が、今回大学を卒業して完答へ社会人として引っ越すわけです。引っ越すことがどう、というわけではありません。別に会えないわけじゃないですから。 その友人が参加する最後の演奏会だろうと思いまして、余計な心遣いだというのは重々承知ですが、なんとか心に残る思い出になれば、と一計を練ったわけです。別にいい思い出になれとも悪い思い出になれとも思っていたわけではありません。なにかしら、その友人に残ればいいな、と。よく言うじゃないですか。モノより思い出って。

先ほどは主な目的ですが、こういうジョイントコンサートのようなイベントに私は疎かったので、だんだん知らない人との関わり合いが増えるのです。これがまたとても楽しくて。 本演奏会がなければ知り合えなかった人がかなりいるわけですので、本当に本演奏会に参加して良かったなぁ、と思います。

謝辞

全員揃ったのは本番以外ありませんでしたが、誰一人欠けても本演奏会のサウンドは奏でられなかったわけです。 だから出会えたことに感謝。 願わくば、私と出会った人に何か与えられたらば…と思います。

演奏会を聴きに来ていただいた方々はもちろんのこと、参加者の皆様、そして本演奏会を企画した人々、本当に感謝しています。 特に統括と会計担当者には、深謝申し上げなければなりません。ありがたやありがたや。彼女らがいなければこの3か月は回らなかった!


日本語がおかしいのは5年前だからなのか、それとも勢いで書いているためなのか。 今年の反省も書きます。