[UVM] メッセージ出力フォーマットを変更する

UVMの`uvm_info等のメッセージ出力マクロは、特に何も指定しなくてもそれを実行したファイル名と行番号、シミュレーション時間などが表示されて便利なのですが、個人的には以下のような不満点があります。

* ファイル名がフルパスで表示されるので、メッセージの一行がとても長くなり見づらい
* 出力メッセージの縦方向のインデントを揃えようと思ってもソースコードの行番号と時刻の文字幅が変わってしまうために揃わない

例えば以下のような感じです。

```plaintext
UVM_INFO proj_dir/test_dir/testbench.sv(68) @ 0.000 ns: uvm_test_top [my_test_c] First
UVM_INFO proj_dir/test_dir/testbench.sv(70) @ 10.000 ns: uvm_test_top [my_test_c] Second
UVM_INFO proj_dir/test_dir/testbench.sv(120) @ 10.000 ns: uvm_test_top [my_test_c] Third
```

UVMにはメッセージ出力のフォーマットを変更する仕組みがありますので、これを利用して以下のような表示にしてみたいと思います。

```plaintext
UVM_INFO testbench.sv(  68) @   0.000 ns: uvm_test_top [my_test_c] First
UVM_INFO testbench.sv(  70) @  10.000 ns: uvm_test_top [my_test_c] Second
UVM_INFO testbench.sv( 120) @  10.000 ns: uvm_test_top [my_test_c] Third
```

# 手順

1. UVM 1.1dの場合はuvm_report_serverをextendsして、1.2の場合はuvm_default_report_serverをextendsして派生クラスを作る。このクラスでcompose_messageというfunctionをオーバーライドする。これが出力メッセージの文字列を生成するfunctionとなる
2. 上記で作成したクラスをnew()したのちuvm_report_server::set_server(my_report_server_c)で使用するように設定する

compose_messageの中身は、UVMのソースコードの該当部分をコピーしてきて、必要な部分を書き換えるのがよいでしょう。
今回の目的の場合、ポイントは以下の通り。

* compose_message内で取得できるファイル名はフルパスです。自分でここからディレクトリ名を削除した文字列を取り出す必要があります
* ソースコードの行番号を固定幅にします。フォーマット指定子の"%4d"で最小幅を4文字に指定できます
* UVMの時間の縦方向が揃わないのは、オリジナルのUVMコードで時間を文字列に変換する時に%0tを使用しているせいです。これを%tに変更すれば揃うようになります。なお%tでの出力フォーマットは$timeformatで決まります。これについては[時間表示のフォーマッティング](https://qiita.com/triggerfish/items/8d5bad12d68d92605c7b)で説明しています。

さて、実際のコードを紹介します。上記の手順は同じなのですが、実際のコードはUVM 1.1dと1.2でかなり異なりますので、別々に紹介します。

# UVM 1.1dの場合

```verilog
import uvm_pkg::*;
`include "uvm_macros.svh"

class my_report_server_c extends uvm_report_server;

  static function string get_filename_wo_directory(string fullname);
    const byte delimiter = "/";
    string stripped_name;

    stripped_name = fullname;
    for (int i = fullname.len() - 1; i >= 0; i--) begin
      if (fullname[i] == delimiter) begin
        stripped_name = fullname.substr(i + 1, fullname.len() - 1);
        break;
      end
    end

    return stripped_name;
  endfunction

  virtual function string compose_message(uvm_severity severity,
                                          string name,
                                          string id,
                                          string message,
                                          string filename,
                                          int line);
    uvm_severity_type sv;
    string time_str;
    string line_str;

    sv = uvm_severity_type'(severity);
    $swrite(time_str, "%t", $realtime);
    filename = get_filename_wo_directory(filename);
    $swrite(line_str, "%4d", line);
    return {sv.name(), " ", filename, "(", line_str, ")", " @ ", time_str, ": ", name, " [", id, "] ", message};
  endfunction
endclass

class my_test_c extends uvm_test;
  `uvm_component_utils(my_test_c)

  function new(string name, uvm_component parent);
    my_report_server_c my_report_server;
    super.new(name, parent);

    my_report_server = new();
    uvm_report_server::set_server(my_report_server);
  endfunction

  virtual task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    repeat (3) begin
      `uvm_info(get_type_name(), "Bra", UVM_NONE)
      #30ns ;
      `uvm_info(get_type_name(), "Bra Bra", UVM_NONE)
      #30ns ;
    end
    phase.drop_objection(this);
  endtask
endclass


module top();
  initial begin
    $timeformat(-9, 3, " ns", 12);
    run_test("my_test_c");
  end
endmodule
```

EDA playgroundの[こちら](https://www.edaplayground.com/x/5Gyw)にサンプルコードがあります。
ただしEDA playgroundではシミュレーションのファイルがルートディレクトリに配置されるイメージになっているため、ファイル名表示をフルパスからファイル名だけに変更する効果を見ることができません。

# UVM 1.2の場合

```verilog
import uvm_pkg::*;
`include "uvm_macros.svh"

class my_report_server_c extends uvm_default_report_server;

  static function string get_filename_wo_directory(string fullname);
    const byte delimiter = "/";
    string stripped_name;

    stripped_name = fullname;
    for (int i = fullname.len() - 1; i >= 0; i--) begin
      if (fullname[i] == delimiter) begin
        stripped_name = fullname.substr(i + 1, fullname.len() - 1);
        break;
      end
    end

    return stripped_name;
  endfunction

  virtual function string compose_report_message(uvm_report_message report_message,
                                                 string report_object_name = "");
    string sev_string;
    uvm_severity l_severity;
    uvm_verbosity l_verbosity;
    string filename_line_string;
    string time_str;
    // string line_str;
    string context_str;
    string verbosity_str;
    string terminator_str;
    string msg_body_str;
    uvm_report_message_element_container el_container;
    string prefix;
    uvm_report_handler l_report_handler;

    l_severity = report_message.get_severity();
    sev_string = l_severity.name();

    $swrite(filename_line_string, "%s(%4d)", get_filename_wo_directory(report_message.get_filename()), report_message.get_line());
    $swrite(time_str, "%t", $time);

    if (report_message.get_context() != "")
      context_str = {"@@", report_message.get_context()};

    if (show_verbosity) begin
      if ($cast(l_verbosity, report_message.get_verbosity()))
        verbosity_str = l_verbosity.name();
      else
        verbosity_str.itoa(report_message.get_verbosity());
      verbosity_str = {"(", verbosity_str, ")"};
    end

    if (show_terminator)
      terminator_str = {" -",sev_string};

    el_container = report_message.get_element_container();
    if (el_container.size() == 0)
      msg_body_str = report_message.get_message();
    else begin
      prefix = uvm_default_printer.knobs.prefix;
      uvm_default_printer.knobs.prefix = " +";
      msg_body_str = {report_message.get_message(), "\n", el_container.sprint()};
      uvm_default_printer.knobs.prefix = prefix;
    end

    if (report_object_name == "") begin
      l_report_handler = report_message.get_report_handler();
      report_object_name = l_report_handler.get_full_name();
    end

    compose_report_message = {sev_string, verbosity_str, " ", filename_line_string, "@ ",
      time_str, ": ", report_object_name, context_str,
      " [", report_message.get_id(), "] ", msg_body_str, terminator_str};

  endfunction
endclass

class my_test_c extends uvm_test;
  `uvm_component_utils(my_test_c)

  function new(string name, uvm_component parent);
    my_report_server_c my_report_server;
    super.new(name, parent);

    my_report_server = new();
    uvm_report_server::set_server(my_report_server);
  endfunction

  virtual task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    repeat (3) begin
      `uvm_info(get_type_name(), "Bra", UVM_NONE)
      #30ns ;
      `uvm_info(get_type_name(), "Bra Bra", UVM_NONE)
      #30ns ;
    end
    phase.drop_objection(this);
  endtask
endclass


module top();
  initial begin
    $timeformat(-9, 3, " ns", 12);
    run_test("my_test_c");
  end
endmodule
```

EDA playgroundの[こちら](https://www.edaplayground.com/x/6MXh)にサンプルコードがあります。
ただしEDA playgroundではシミュレーションのファイルがルートディレクトリに配置されるイメージになっているため、ファイル名表示をフルパスからファイル名だけに変更する効果を見ることができません。

# 参考文献
Accellera, "6.4 UVM Report Server", [Universal Verification Methodology (UVM) 1.2 Class Reference]
(https://www.accellera.org/images/downloads/standards/uvm/UVM_Class_Reference_Manual_1.2.pdf), pp.70-80.

Accellera, "6.3 UVM Report Server", [Universal Verification Methodology (UVM) 1.1 Class Reference]
(https://www.accellera.org/images/downloads/standards/uvm/UVM_1.1_Class_Reference_Final_06062011.pdf), pp.61-65.