[SystemVerilog]Jenkinsを利用したUVMテスト環境の構築


#はじめに、動機
SystemVerilogのUVMは、テストのためのフレームワークと呼べるわけで、継続的インテグレーション(CI)を使用したブロックテストができないかと思いました。
CIといえばJenkinsですね。HDLを使うとなると、こんなイメージでしょうか。

![flow.png](https://qiita-image-store.s3.amazonaws.com/0/32925/82e5a6fc-b968-8d2e-4552-88f6c3dcf30b.png)

ソースコード管理システムにHDLコードをCommitして、それをトリガとしてテスト実施し、結果を得る仕組みです。
JenkinsとUVMを使うと、Commit後フローは自動化され、かつたくさんのテストシナリオをスケーラブルに流し込めるなと思い、まずは環境を構築してみることにしました。

OSはCentOS 6.5です。

#必要なツール
##HDLシミュレータ
SystemVerilogに対応した適当なやつです。
##ソースコード管理ツール
普通はGitだろ、って感じですが、ここではBazaarを使います。自分がBazaar好きなんですよね。[死兆星](https://lists.gnu.org/archive/html/emacs-devel/2014-01/msg00005.html)が見えますが。
##Webサーバ
Apache2です。
##CIツール
Jenkinsです。

#構築作業
##Jenkinsのインストール
[こちら](http://oscasierra.net/2013/05/jenkins-to-redhat/)を参考にしました。
/etc/sysconfig/jenkinsの中を見ると、$JENKINS_HOMEは、`/var/lib/jenkins`に設定されているようです。

###追加したプラグイン
インストールされたJenkinsに、下記のプラグインを追加しました。

* Bazaar plugin
* Python Plugin
* SCM API Plugin
* Simple Theme Plugin
* xUnit Plugin

###Bazzarのインストール

```sh
sudo yum install bzr
```

##Apache+Bazaar環境構築
[公式ドキュメント](http://doc.bazaar.canonical.com/beta/ja/user-guide/http_smart_server.html)を参考にしました。
CentOSだとあまり情報がなかったので、ApacheとWSGIをインストールし、Bazaarのスマートサーバを立てるまでをメモしておきます。

###Apache, WSGIインストール
```sh
sudo yum install httpd mod_wsgi
```

###Apacheの設定ファイル
ここに、公式ドキュメントにあるWSGI(mod_wsgi)設定を追加します。
自分の場合、CentOSのApacheのhtmlディレクトリ直下に、リポジトリ保存場所として「bzr」ディレクトリを作成しました。

```sh:/etc/httpd/conf/httpd.conf
WSGIScriptAliasMatch ^/code/.*/\.bzr/smart$ /var/www/cgi-bin/bzr.wsgi

RewriteEngine On
RewriteCond %{REQUEST_URI} !^/code/.*/\.bzr/smart$
RewriteRule ^/code/(.*/\.bzr/.*)$ /var/www/html/bzr/$1 [L]

<Directory "/var/www/html/bzr">
    WSGIApplicationGroup %{GLOBAL}
</Directory>
```

###WSGIの設定
これも公式通りです。

```python:/var/www/cgi-bin/bzr.wsgi
from bzrlib.transport.http import wsgi

def application(environ, start_response):
    app = wsgi.make_app(
        root="/var/www/html/bzr",
        prefix="/bzr",
        readonly=False,
        load_plugins=True,
        enable_logging=False)
    return app(environ, start_response)
```

###Apacheの有効化

```sh
sudo /sbin/chkconfig httpd on
sudo /etc/init.d/httpd start
```

#リポジトリ作成・Jenkinsによるテスト実行

##リポジトリの作成、add, commit
共用リポジトリを「uvm_example」、ブランチを「trunk」として作成します。

###リポジトリ作成

```sh
cd /var/www/html/bzr
bzr init-repository uvm_example
cd uvm_example
bzr init trunk
```

###add, commit
とりあえずUVM1.1dを入れてみます。

```sh
cp -pr $UVM_HOME/.. .
bzr add uvm-1.1d
bzr commit -m "uvm init" uvm-1.1d
```

##Jenkinsのジョブ作成・設定
SystemVerilog関連のテンプレートなどありませんから、「フリースタイル・プロジェクトのビルド」を使います。
主に設定した箇所を取り上げます。Web画面は以下の感じです。

![jnkns2.png](https://qiita-image-store.s3.amazonaws.com/0/32925/8ada042b-ec92-0be2-24e3-50fad7e3a29a.png)

###ソースコード管理
Bazaar pluginを入れたので、ソースコード管理にBazaarが出てきます。
ここにブランチ（共用リポジトリではない）URLを入れます。

###SCMポーリング
Bazaarでソースコードやスクリプトをcommitすると、ブランチが更新されるので、Jenkinsがこれを検知して、自動的にビルドを開始させます。ここでは5分間隔でポーリングさせます。
初めは、commit直後にJenkinsにpushさせてビルドを走らせようと考えましたが、うまくいきませんでした。

###ビルド
「シェルの実行」を使います。
どうもJenkinsは、ユーザーがJenkinsで実行するためか、ツールへのPATHとか再設定しなくてはならないため、シェルスクリプトにまとめました。
PATH設定が、「eda.bashrc」になります。


```sh:build.bash
source  env/eda.bashrc

cd sim

make -f Makefile.vcs
```

シェルスクリプトから呼び出すMakeファイルはこんな感じで。
ここではシミュレーターとしてVCSを想定していますが、他のシミュレーターを使う場合は`$UVM_HOME/example`にあるの`Makefile.ius`や`Makefile.questa`を参考にして下さい。
ここでは、`$UVM_HOME/examples/integrated/ubus`に記載されているサンプルを使っています。XML関係は後述します。

```mf:Makefile.vcs
UVM_HOME=/usr/uvm/uvm-1.1d
DUT     =/var/www/html/bzr/uvm_sample/trunk/uvm-1.1d
XML     = /var/www/html/bzr/uvm_sample/trunk/xml

include $(UVM_HOME)/examples/Makefile.vcs

all: comp run

comp:
        $(VCS) \
        -full64 \
        +incdir+$(DUT)/examples/integrated/ubus/sv \
        +incdir+$(DUT)/examples/integrated/ubus/examples \
        +incdir+$(XML) \
        $(XML)/xml_pkg.sv \
        $(DUT)/examples/integrated/ubus/examples/ubus_tb_top.sv

run:
        $(SIMV) +UVM_TESTNAME=test_2m_4s
        $(CHECK)


clean:
        @rm -fr \
          csrc \
          simv* \
          *.log \
          *.key \
          vc_hdrs.h \
          log.xml
```

この辺、[Scons](http://www.scons.org/)にしたい…。

###ブランチへのCommit
話は前後しますが、上記のスクリプトの入ったsimやenvなどのディレクトリもあらかじめ作った後にCommitしておきました。

```sh
cd /var/www/html/bzr/trunk
bzr add sim env
bzr commit -m "test environment" sim env xml
```

##Jenkins実行
左にある「ビルドの実行」をクリックすると、ビルド履歴が動き出し、最終的に青になればテストはパスします。

![jnkns1.png](https://qiita-image-store.s3.amazonaws.com/0/32925/e742138a-e0e4-e714-c9f3-7fb2775ece25.png)

Console Outputをクリックすると、実行ログが出てきます。

##SCMポーリングによるJenkins実行
SCMで5分間隔でブランチを監視しているので、Bazaarでブランチへcommitすると、Jenkinsがこれを見つけて自動的にビルドを実行させます。
以下の図は、Bazaarのブランチを5分ごとにチェックし、ブランチに変化がなかったので何もしなかった例です。

![jnkns3.png](https://qiita-image-store.s3.amazonaws.com/0/32925/8727d321-ba23-aaf5-eaf1-4d26c5156cf8.png)

commitを実施すると、アップデートがあったとログに記されます。

![jnkns4.png](https://qiita-image-store.s3.amazonaws.com/0/32925/fe6af28b-ac0e-52aa-6f9f-b3d4bf58d588.png)

アップデートで自動実行されたビルドは、こんな感じでSCMポーリングで実行したよと知らせてくれます。

![jnkns5.png](https://qiita-image-store.s3.amazonaws.com/0/32925/023b6cae-0b4b-17d5-df24-9c8924ce3c89.png)

##XML変換・レポート取得
いろいろ調べていたら、Verilab社がJenkinsやXMLと、メソドロジを組み合わせてやっていることがわかりました。

###Verilab社の取り組み
* OVM環境でビルド実行後にPerlスクリプトにて、JUnit形式のXMLファイルを出力した例。(DVCON 2012)
http://www.verilab.com/files/dvcon2012_ci_gray_mcgregor.pdf

* UVM環境にて`uvm_report_server`を継承し、SystemVerilogからXMLを出力させ、XMLをPythonでパースし、テスト結果を見やすくした例。(SNUG Austin 2013)
http://www.verilab.com/files/SNUG_Applications_of_custom_UVM_report_servers.pdf
* JenkinsとCadenceのvManagerを連携させ、vManagerのテスト結果(CSV)をPythonでパースし、JenkinsでCIを実施した例。(DVCON 2014)
http://www.verilab.com/files/DVCon2014_CI_and_MDV_finalpaper.pdf
http://www.verilab.com/files/DVCon2014_CI_and_MDV_as_presented.pdf

 **上記のうち、下の二つはBitbucketにてApache2.0形式で公開されています。**
https://bitbucket.org/verilab/

###XMLを出力させてみる。
2番目に紹介されているSystemVerilogからXMLを出力させるユーティリティを組み込んでみました。
元ソースはこれです。

```verilog:xml_report_server.svh
//----------------------------------------------------------------------
//   Copyright 2012 Verilab Inc.
//   Gordon McGregor (gordon.mcgregor@verilab.com)
//
//   All Rights Reserved Worldwide
//
//   Licensed under the Apache License, Version 2.0 (the
//   "License"); you may not use this file except in
//   compliance with the License.  You may obtain a copy of
//   the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in
//   writing, software distributed under the License is
//   distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
//   CONDITIONS OF ANY KIND, either express or implied.  See
//   the License for the specific language governing
//   permissions and limitations under the License.
//----------------------------------------------------------------------

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

// Class: xml_report_server
//
// Replacement for uvm_report_server to log messages to an XML format
// that can be more easily reused and manipulated by external tools/ viewers
//
// Basic XML schema
// <log>
//   <msg verbosity="val" severity="string" file="filename" line="val" id="string" time="val" context="string(optional)">text</msg>
//   ...
//   <msg verbosity="val" severity="string" file="filename" line="val" id="string" time="val" context="string(optional)">text</msg>
//   <msg verbosity="val" severity="string" file="filename" line="val" id="string" time="val" context="string(optional)">text</msg>
//   ...
// </log>
//
class xml_report_server extends uvm_report_server;

  uvm_report_server old_report_server;
  uvm_report_global_server global_server;

  // characters that are invalid XML that have to be encoded
  string replacements[string] = '{ "<" : "&lt;",
                                   "&" : "&amp;",
                                   ">" : "&gt;",
                                   "'" : "&apos;",
                                   "\"": "&quot;"
                                 };
  integer logfile_handle;

  //Function: new
  // constructor
  function new(string name = "xml_report_server", log_filename = "log.xml");
    super.new();
    set_name(name);

    global_server = new();
    install_server();
    logfile_handle = $fopen(log_filename, "w");
    report_header(logfile_handle);
  endfunction

  // Function: install_server
  // replace the global server with this server
  function void install_server;
    old_report_server = global_server.get_server();
    global_server.set_server(this);
  endfunction

  // Function: enable_xml_logging
  // Configure all components to use UVM_LOG actions to trigger XML capture
  // has to be called after components have been instantiated (end of elaboration, run etc)
  function void enable_xml_logging(uvm_component base=null);
    uvm_root top;

    if (base == null) begin
      top = uvm_root::get();
      base = top;
    end

    base.set_report_default_file_hier(logfile_handle);
    base.set_report_severity_action_hier(UVM_INFO, UVM_DISPLAY | UVM_LOG);
    base.set_report_severity_action_hier(UVM_WARNING, UVM_DISPLAY | UVM_LOG);
    base.set_report_severity_action_hier(UVM_ERROR,   UVM_DISPLAY | UVM_LOG | UVM_COUNT);
    base.set_report_severity_action_hier(UVM_FATAL,   UVM_DISPLAY | UVM_LOG | UVM_EXIT);
  //  base.get_report_handler().dump_state();
  endfunction

  // Function: convert_verbosity_to_string
  // Helper function to convert verbosity value to appropriate string, based on uvm_verbosity enum if an equivalent level
  function string convert_verbosity_to_string(int verbosity);
    uvm_verbosity l_verbosity;

    if ($cast(l_verbosity, verbosity)) begin
        convert_verbosity_to_string = l_verbosity.name();
    end else begin
        string l_str;
        l_str.itoa(verbosity);
        convert_verbosity_to_string = l_str;
    end
  endfunction

  // Function: report_header
  // Output standard XML header to log file
  function void report_header(UVM_FILE file = 0);
    f_display(file, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?xml-stylesheet type=\"text/xsl\" href=\"uvm.xsl\"?><log>\n");
  endfunction

  // Function: report_footer
  // Output XML closing tags to log file
  function void report_footer(UVM_FILE file = 0);
    integer result;
    $fflush(file);
    result = $fseek(logfile_handle, 0, 2);
    f_display(logfile_handle, "</log>");
  endfunction

  // Function: summarize
  // tidy up logging and restore global report server
  function void summarize(UVM_FILE file = 0);
    report_footer();
    global_server.set_server(old_report_server);
    $fclose(logfile_handle);
    old_report_server.report_summarize(file);
    old_report_server.summarize(file);
  endfunction

  // Function: process_report
  //
  // Processes the message's actions.
  virtual function void process_report(
    uvm_severity severity,
    string name,
    string id,
    string message,
    uvm_action action,
    UVM_FILE file,
    string filename,
    int line,
    string composed_message,
    int verbosity_level,
    uvm_report_object client
    );
    // update counts
    incr_severity_count(severity);
    incr_id_count(id);

    if(action & UVM_DISPLAY)
      $display("%s",composed_message);

    // if log is set we need to send to the file but not resend to the
    // display. So, we need to mask off stdout for an mcd or we need
    // to ignore the stdout file handle for a file handle.
    if(action & UVM_LOG)
      if( (file == 0) || (file != 32'h8000_0001) ) //ignore stdout handle
      begin
        UVM_FILE tmp_file = file;
        if( (file&32'h8000_0000) == 0) //is an mcd so mask off stdout
        begin
          tmp_file = file & 32'hffff_fffe;
        end
        composed_message = compose_xml_message(severity, verbosity_level, name, id, message, filename, line);
        f_display(tmp_file, composed_message);
      end

    if(action & UVM_EXIT) client.die();

    if(action & UVM_COUNT) begin
      if(get_max_quit_count() != 0) begin
        incr_quit_count();
        if(is_quit_count_reached()) begin
          client.die();
        end
      end
    end

    if (action & UVM_STOP) $stop;

  endfunction

  // Function: sanitize
  //
  // Given an unencoded input string, replaces illegal characters for XML data format
  function string sanitize(string data);

    for(int i = data.len()-1; i >= 0; i--) begin
      if (replacements.exists(data[i])) begin
          data = {data.substr(0,i-1), replacements[data[i]], data.substr(i+1, data.len()-1)};
      end
    end
    return data;
  endfunction : sanitize


  // Function: xla
  // XML Attribute
  // Generate an XML attribute ( tag = "data" )
  function string xla(string tag, string data);
    xla="";
    if (data != "") begin
      xla = {" ", tag, "=\"", sanitize(data), "\" "};
    end
  endfunction

    // Function: xle
    // XML Element
    // Generate an XML element ( <tag attributes>data</tag> )
  function string xle(string tag, string data, string attributes="");
    xle = "";
    if (data != "") begin
      xle = {"<", tag, attributes, ">", sanitize(data), "</", tag, ">\n"};
    end
  endfunction

  // Function: compose_log_report_message
  // Generate the XML encapsulated report message, for logging
  virtual function string compose_xml_message(
    uvm_severity severity,
    int verbosity,
    string name,
    string id,
    string message,
    string filename,
    int    line
    );
    uvm_severity_type sv;
    string severity_string;
    string time_str;
    string line_str;
    string verbosity_str;
    integer result;

    sv = uvm_severity_type'(severity);
    severity_string = sv.name();
    $swrite(time_str, "%0t", $time);
    line_str.itoa(line);
    verbosity_str.itoa(verbosity);

    compose_xml_message = xle("msg", message,
                             {xla("verbosity", verbosity_str),
                              xla("severity", severity_string),
                              xla("file", filename),
                              xla("line", line_str),
                              xla("id", id),
                              xla("time", time_str),
                              xla("context", name) });
  endfunction


endclass
```

このxml_report_serverクラスを、uvm_envクラスを継承している`$UVM_HOME/examples/integrated/ubus/examples/ubus_examples_tb.sv`にインスタンスさせ、end_of_elaboration_phaseに追記します。

```verilog:ubus_expamples_tb.sv
//----------------------------------------------------------------------
//   Copyright 2007-2010 Mentor Graphics Corporation
//   Copyright 2007-2011 Cadence Design Systems, Inc.
//   Copyright 2010 Synopsys, Inc.
//   All Rights Reserved Worldwide
//
//   Licensed under the Apache License, Version 2.0 (the
//   "License"); you may not use this file except in
//   compliance with the License.  You may obtain a copy of
//   the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in
//   writing, software distributed under the License is
//   distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
//   CONDITIONS OF ANY KIND, either express or implied.  See
//   the License for the specific language governing
//   permissions and limitations under the License.
//----------------------------------------------------------------------

`include "ubus_example_scoreboard.sv"
`include "ubus_master_seq_lib.sv"
`include "ubus_example_master_seq_lib.sv"
`include "ubus_slave_seq_lib.sv"

`include "xml_report_server.svh"

//------------------------------------------------------------------------------
//
// CLASS: ubus_example_tb
//
//------------------------------------------------------------------------------

class ubus_example_tb extends uvm_env;

  // Provide implementations of virtual methods such as get_type_name and create
  `uvm_component_utils(ubus_example_tb)

  // ubus environment
  ubus_env ubus0;

  // Scoreboard to check the memory operation of the slave.
  ubus_example_scoreboard scoreboard0;

  // XML logging server
  xml_report_server xml_reporter;


  // new
  function new (string name, uvm_component parent=null);
    super.new(name, parent);
    xml_reporter = new();
  endfunction : new

  // build_phase
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    uvm_config_db#(int)::set(this,"ubus0",
			       "num_masters", 1);
    uvm_config_db#(int)::set(this,"ubus0",
			       "num_slaves", 1);

    ubus0 = ubus_env::type_id::create("ubus0", this);
    scoreboard0 = ubus_example_scoreboard::type_id::create("scoreboard0", this);
  endfunction : build_phase

  function void connect_phase(uvm_phase phase);
    // Connect slave0 monitor to scoreboard
    ubus0.slaves[0].monitor.item_collected_port.connect(
      scoreboard0.item_collected_export);
  endfunction : connect_phase

  function void end_of_elaboration_phase(uvm_phase phase);
    // Set up slave address map for ubus0 (basic default)
    ubus0.set_slave_address_map("slaves[0]", 0, 16'hffff);

    // Enable XML logging 
    xml_reporter.enable_xml_logging();
  endfunction : end_of_elaboration_phase

endclass : ubus_example_tb
```

XML出力関係は、後々拡張のためにPackege化させます。

```sv:xml_pkg.sv
package xml_pkg;
  import uvm_pkg::*;

  `include "xml_report_server.svh"
endpackage
```

###XML出力
ここまでのコードをCommitさせ、Jenkinsにて実行させると、下記のような感じでXMLが書き出されました。

```xml:log.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="uvm.xsl"?><log>

<msg verbosity="100"  severity="UVM_INFO"  file="/var/www/html/bzr/uvm_sample/trunk/uvm-1.1d/examples/integrated/ubus/ex
amples/test_lib.sv"  line="55"  id="test_2m_4s"  time="0"  context="uvm_test_top" >Printing the test topology :
--------------------------------------------------------------------
Name                         Type                     Size  Value   
--------------------------------------------------------------------
uvm_test_top                 test_2m_4s               -     @459    
  ubus_example_tb0           ubus_example_tb          -     @493    
    scoreboard0              ubus_example_scoreboard  -     @513    
      item_collected_export  uvm_analysis_imp         -     @521    
      disable_scoreboard     integral                 1     &apos;h0     
      num_writes             integral                 32    &apos;d0     
      num_init_reads         integral                 32    &apos;d0     
      num_uninit_reads       integral                 32    &apos;d0     
      recording_detail       uvm_verbosity            32    UVM_FULL
    ubus0                    ubus_env                 -     @505    
      bus_monitor            ubus_bus_monitor         -     @535    
      masters[0]             ubus_master_agent        -     @567    
      masters[1]             ubus_master_agent        -     @579    
      slaves[0]              ubus_slave_agent         -     @590    
      slaves[1]              ubus_slave_agent         -     @598    
      slaves[2]              ubus_slave_agent         -     @606    
      slaves[3]              ubus_slave_agent         -     @614    
      has_bus_monitor        integral                 1     &apos;h1     
      num_masters            integral                 32    &apos;h2     
      num_slaves             integral                 32    &apos;h4     
      intf_checks_enable     integral                 1     &apos;h1     
      intf_coverage_enable   integral                 1     &apos;h1     
      recording_detail       uvm_verbosity            32    UVM_FULL
    recording_detail         uvm_verbosity            32    UVM_FULL
--------------------------------------------------------------------
</msg>

<msg verbosity="0"  severity="UVM_INFO"  file="/var/www/html/bzr/uvm_sample/trunk/uvm-1.1d/examples/integrated/ubus/exam
ples/ubus_example_master_seq_lib.sv"  line="236"  id="loop_read_modify_write_seq"  time="0"  context="uvm_test_top.ubus_
example_tb0.ubus0.masters[0].sequencer@@loop_read_modify_write_seq" >loop_read_modify_write_seq starting...itr = 6</msg>

<msg verbosity="0"  severity="UVM_INFO"  file="/var/www/html/bzr/uvm_sample/trunk/uvm-1.1d/examples/integrated/ubus/exam
ples/ubus_example_master_seq_lib.sv"  line="236"  id="loop_read_modify_write_seq"  time="0"  context="uvm_test_top.ubus_
example_tb0.ubus0.masters[1].sequencer@@loop_read_modify_write_seq" >loop_read_modify_write_seq starting...itr = 8</msg>

(中略)

<msg verbosity="100"  severity="UVM_INFO"  file="/var/www/html/bzr/uvm_sample/trunk/uvm-1.1d/examples/integrated/ubus/sv/ubus_slave_monitor.sv"  line="243"  id="uvm_test_top.ubus_example_tb0.ubus0.slaves[3].monitor"  time="3030"  context="uvm_test_top.ubus_example_tb0.ubus0.slaves[3].monitor" >Covergroup &apos;cov_trans&apos; coverage: 30.000000</msg>

<msg verbosity="0"  severity="UVM_INFO"  file="/var/www/html/bzr/uvm_sample/trunk/uvm-1.1d/examples/integrated/ubus/examples/test_lib.sv"  line="70"  id="test_2m_4s"  time="3030"  context="uvm_test_top" >** UVM TEST PASSED **</msg>

</log>
```


#おわりに
Jenkinsは分散ビルド・テスト等があるため、UVMのUVM_TESTやUVM_SEQUENCEと組み合わせると、シミュレーターのライセンス数に依りますが、多数のテストを同時実行しかつスケーラビリティに富む環境が構築可能と思っています。
もっとも、スケーラビリティがあるということは構築は大変なんですけどね。

後日、XMLによるレポート出力をJunitで読める形式に合わせ、Jenkinsで統計が取れるようにしていこうと思います。