#はじめに、動機
SystemVerilogのUVMは、テストのためのフレームワークと呼べるわけで、継続的インテグレーション(CI)を使用したブロックテストができないかと思いました。
CIといえばJenkinsですね。HDLを使うとなると、こんなイメージでしょうか。
ソースコード管理システムにHDLコードをCommitして、それをトリガとしてテスト実施し、結果を得る仕組みです。
JenkinsとUVMを使うと、Commit後フローは自動化され、かつたくさんのテストシナリオをスケーラブルに流し込めるなと思い、まずは環境を構築してみることにしました。
OSはCentOS 6.5です。
#必要なツール
##HDLシミュレータ
SystemVerilogに対応した適当なやつです。
##ソースコード管理ツール
普通はGitだろ、って感じですが、ここではBazaarを使います。自分がBazaar好きなんですよね。死兆星が見えますが。
##Webサーバ
Apache2です。
##CIツール
Jenkinsです。
#構築作業
##Jenkinsのインストール
こちらを参考にしました。
/etc/sysconfig/jenkinsの中を見ると、$JENKINS_HOMEは、/var/lib/jenkins
に設定されているようです。
###追加したプラグイン
インストールされたJenkinsに、下記のプラグインを追加しました。
- Bazaar plugin
- Python Plugin
- SCM API Plugin
- Simple Theme Plugin
- xUnit Plugin
###Bazzarのインストール
sudo yum install bzr
##Apache+Bazaar環境構築
公式ドキュメントを参考にしました。
CentOSだとあまり情報がなかったので、ApacheとWSGIをインストールし、Bazaarのスマートサーバを立てるまでをメモしておきます。
###Apache, WSGIインストール
sudo yum install httpd mod_wsgi
###Apacheの設定ファイル
ここに、公式ドキュメントにあるWSGI(mod_wsgi)設定を追加します。
自分の場合、CentOSのApacheのhtmlディレクトリ直下に、リポジトリ保存場所として「bzr」ディレクトリを作成しました。
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の設定
これも公式通りです。
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の有効化
sudo /sbin/chkconfig httpd on
sudo /etc/init.d/httpd start
#リポジトリ作成・Jenkinsによるテスト実行
##リポジトリの作成、add, commit
共用リポジトリを「uvm_example」、ブランチを「trunk」として作成します。
###リポジトリ作成
cd /var/www/html/bzr
bzr init-repository uvm_example
cd uvm_example
bzr init trunk
###add, commit
とりあえずUVM1.1dを入れてみます。
cp -pr $UVM_HOME/.. .
bzr add uvm-1.1d
bzr commit -m "uvm init" uvm-1.1d
##Jenkinsのジョブ作成・設定
SystemVerilog関連のテンプレートなどありませんから、「フリースタイル・プロジェクトのビルド」を使います。
主に設定した箇所を取り上げます。Web画面は以下の感じです。
###ソースコード管理
Bazaar pluginを入れたので、ソースコード管理にBazaarが出てきます。
ここにブランチ(共用リポジトリではない)URLを入れます。
###SCMポーリング
Bazaarでソースコードやスクリプトをcommitすると、ブランチが更新されるので、Jenkinsがこれを検知して、自動的にビルドを開始させます。ここでは5分間隔でポーリングさせます。
初めは、commit直後にJenkinsにpushさせてビルドを走らせようと考えましたが、うまくいきませんでした。
###ビルド
「シェルの実行」を使います。
どうもJenkinsは、ユーザーがJenkinsで実行するためか、ツールへのPATHとか再設定しなくてはならないため、シェルスクリプトにまとめました。
PATH設定が、「eda.bashrc」になります。
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関係は後述します。
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にしたい…。
###ブランチへのCommit
話は前後しますが、上記のスクリプトの入ったsimやenvなどのディレクトリもあらかじめ作った後にCommitしておきました。
cd /var/www/html/bzr/trunk
bzr add sim env
bzr commit -m "test environment" sim env xml
##Jenkins実行
左にある「ビルドの実行」をクリックすると、ビルド履歴が動き出し、最終的に青になればテストはパスします。
Console Outputをクリックすると、実行ログが出てきます。
##SCMポーリングによるJenkins実行
SCMで5分間隔でブランチを監視しているので、Bazaarでブランチへcommitすると、Jenkinsがこれを見つけて自動的にビルドを実行させます。
以下の図は、Bazaarのブランチを5分ごとにチェックし、ブランチに変化がなかったので何もしなかった例です。
commitを実施すると、アップデートがあったとログに記されます。
アップデートで自動実行されたビルドは、こんな感じでSCMポーリングで実行したよと知らせてくれます。
##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を出力させるユーティリティを組み込んでみました。
元ソースはこれです。
//----------------------------------------------------------------------
// 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] = '{ "<" : "<",
"&" : "&",
">" : ">",
"'" : "'",
"\"": """
};
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に追記します。
//----------------------------------------------------------------------
// 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化させます。
package xml_pkg;
import uvm_pkg::*;
`include "xml_report_server.svh"
endpackage
###XML出力
ここまでのコードをCommitさせ、Jenkinsにて実行させると、下記のような感じで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 'h0
num_writes integral 32 'd0
num_init_reads integral 32 'd0
num_uninit_reads integral 32 '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 'h1
num_masters integral 32 'h2
num_slaves integral 32 'h4
intf_checks_enable integral 1 'h1
intf_coverage_enable integral 1 '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 'cov_trans' 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で統計が取れるようにしていこうと思います。