FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

XMLとStemsを使ってVerilatorのRTLシミュレーション結果をGtkWaveでアノテーション表示する方法

VerilatorはVerilogコンパイルしてシミュレーションするためのツールだが、波形ログとしてはVCDやFSTなどを出力することができる。VCDはかなり昔から使われている波形保存のためのフォーマット、FSTはVCDと違って圧縮されたより高速アクセスを実現するためのフォーマットだ。

  • VCD : Value Change Dump
    • 波形ファイル。バイナリ圧縮されていないので中身を目視で読むことができる。
  • FST
    • Fast Signal Trace. バイナリ圧縮された波形フォーマット形式。

これに加えて、VerilatorはXMLファイルを出力することができる。XMLファイルにはVerilogの階層構造などの情報が格納されており、Verilogファイルの解析結果などがXML形式で出力される。

$ verilator --version
Verilator 4.008 2018-12-01 rev UNKNOWN_REV
$ verilator --help
        --x-initial-edge            Enable initial X->0 and X->1 edge triggers
        --xml-only                  Create XML parser output
         -y <dir>                   Directory to search for modules

これを使えば、GtkWaveにて波形を表示する際にVerilogファイルとの関係を表示しつつ、現在の信号の値をVerilogファイルにアノテーションしながら表示することができるようだ。やり方を調査してみよう。

  • 使用するVerilator : 4.008 2018-12-01 (後述するが Verilator Version 3系 (3.924 2018-06-12)とはXMLのフォーマットが違うので注意)
  • 使用するGtkWave : v3.3.103

今回は、TileLinkの単体テスト用に使っている環境を使用する。

f:id:msyksphinz:20200107233825p:plain
XMLとStemsを使ったRTLアノテーションのフロー

VerilatorでVerilogの階層構造をXMLで出力する

上記の通り、XML形式のVerilogファイルの階層構造情報を出力したい場合、Verilatorのコンパイルオプションに--xml-onlyを付け加える。

  • Makefile
tilelink-xml: TestHarness.sv verilator_bin
        mkdir -p $(generated_dir_debug)/$(long_name)
        $(VERILATOR) $(VERILATOR_FLAGS) --xml-only -Mdir $(generated_dir_debug)/$(long_name) \
        -o $(abspath $(sim_dir))/$@ $(verilog) $(cppfiles) -LDFLAGS "$(LDFLAGS)" \
        -CFLAGS "-I$(generated_dir_debug) -include $(model_header_debug)"

これでVerilatorによるコンパイル時にXMLファイルが生成される。今回のテスト環境ではgenerated-src-debug/freechips.rocketchip.unittest.TLManyMasterManySlaveTestConfig/VTestHarness.xmlが生成された。

  • generated-src-debug/freechips.rocketchip.unittest.TLManyMasterManySlaveTestConfig/VTestHarness.xml
<?xml version="1.0" ?>
<!-- DESCRIPTION: Verilator output: XML representation of netlist -->
<verilator_xml>
  <files>
    <file id="e" filename="/home/msyksphinz/work/riscv/chisel-development/chisel-hw/TestHarness.sv" language="1800-2017"/>
      ...
  <cells>
    <cell fl="e21582" name="TestHarness" submodname="TestHarness" hier="TestHarness">
      <cell fl="e21590" name="UnitTestSuite" submodname="UnitTestSuite" hier="TestHarness.UnitTestSuite">
        <cell fl="e21517" name="tests_0" submodname="TLOriginalRAMSimpleTest" hier="TestHarness.UnitTestSuite.tests_0">
          <cell fl="e21442" name="SimpleTimer" submodname="SimpleTimer" hier="TestHarness.UnitTestSuite.tests_0.SimpleTimer"/>
...

xml2stemsでstemsファイルに変換する

次にstemsファイルというものを生成する。stemsファイルはGtkWaveでアノテーションを行うためのファイルで、これはXMLファイルから生成する必要がある。これにはxml2stemsというGtkWaveに付属のコマンドを使用するのだが、このコマンドが曲者で、Verilator 4.x系の生成するXMLファイルに対応していない。Verilator 3.x系を使えば問題ないと思うのだが、どうも<module ...>内に指定される要素の数の違いによりxml2stemsが正しくファイルを生成できない様子だった。

とりあえず、GtkWave 3.3.103内のcontrib/xml2stems/xml2stems.ccを改造することで事なきを得た。

diff contrib/xml2stems/xml2stems.cc contrib/xml2stems/xml2stems.cc.orig
76c76
<                                                       if(numqt == 8) break;
---
>                                                       if(numqt == 6) break;
81c81
<                                       if(numqt == 8)
---
>                                       if(numqt == 6)
85c85
<                                                       numqt = 6;
---
>                                                       numqt = 4;
90c90
<                                       if(numqt == 6)
---
>                                       if(numqt == 4)

要するにXMLファイルの要素数が違うのでエラーが発生していたのだが、正直xml2stemsの実装も目に余るひどさなので、XMLファイルを解析するためのライブラリなどを使って書き直したいところだ。

という訳でxml2stemsを使ってgenerated-src-debug/freechips.rocketchip.unittest.TLManyMasterManySlaveTestConfig/VTestHarness.xmlをstemsに変換すると以下のようになる。

xml2stems -V generated-src-debug/freechips.rocketchip.unittest.TLManyMasterManySlaveTestConfig/VTestHarness.xml VTestHarness.stems
  • VTestHarness.stems
++ module TestHarness file /home/msyksphinz/work/riscv/chisel-development/chisel-hw/TestHarness.sv lines 20192 - 20192
++ module TOP file (VerilatorTop) lines 1 - 1
++ comp TestHarness type TestHarness parent TOP
++ comp UnitTestSuite type UnitTestSuite parent TestHarness
++ module UnitTestSuite file /home/msyksphinz/work/riscv/chisel-development/chisel-hw/TestHarness.sv lines 20111 - 20111
++ comp tests_0 type TLManyMasterManySlaveTest parent UnitTestSuite
++ module TLManyMasterManySlaveTest file /home/msyksphinz/work/riscv/chisel-development/chisel-hw/TestHarness.sv lines 20034 - 20034
++ comp SimpleTimer type SimpleTimer parent TLManyMasterManySlaveTest
++ comp lazy_dut type TLManyMasterManySlave parent TLManyMasterManySlaveTest
++ module SimpleTimer file /home/msyksphinz/work/riscv/chisel-development/chisel-hw/TestHarness.sv lines 1 - 1
...

これで必要なファイルはすべて整った。Verilatorでシミュレーションを実行してFSTファイルを生成し、GtkWaveで確認してみよう。

gtkwave -t VTestHarness.stems simx.fst
f:id:msyksphinz:20200107233903p:plain
XMLとStemsを使ったRTLアノテーションの様子。rtlbrowseを使ってRTLとの一致を確認できる。

上手く行ったようだ。これでデバッグをより捗らせることができる。

高速Verilogシミュレータ"Verilator"で「出来ないこと」

f:id:msyksphinz:20170201002310j:plain

本当は、 NVDLAをVerilatorで動かしてみたかったんです。。

NVDLAがVivado Simulatorに対応になったところで、ついでにVerilatorでNVDLAを動かすことは出来るのだろうかと考え、移植を試みた。 結論から言うと、動作させることはできていない。 これはVerilatorの「高速動作」であるが故のさまざまな制限があるからである。

これまではVerilatorは魔法のVerilog Simulatorくらいに思っていたけど(RISC-VのRocket Chipも動かすことが出来るくらいだし!)、Verilatorは意外と言語上の制約が多いと言うことが分かってきた。 日本語でこのような情報をまとめているものは無かったので、せっかくなのでまとめておこう。

ちなみに、マニュアルをざっと読みながらまとめたので、正しい情報は、以下を参照してください。

www.veripool.org

個人的には、以下が猛烈に大きい。

  • 全ての遅延記述 (#) は無視される。
  • event系のイベント (waitなど) はサポートされない。

言語上の制約

Verilatorでコンパイルできるのは、合成用の言語のみ

Verilatorは合成用の言語記述と、それに付随するいくつかのサポート言語、例えば$stop, $finish, $display程度しかサポートされていない。 従って、階層の参照や、イベント処理などの言語拡張は使用することが出来ない。

always @ (x) y = x & z;

上記の用の記述でも、xもしくはzが変化するとyの値が更新される(本当のシミュレータでは、xが更新された場合のみyが更新される)

Bind

Verilatorはbind記述はターゲットモジュール名のみサポートしており、インスタンスパスはサポートしない。

ドット記述による階層参照

Verilatorは変数、関数およびタスクの階層参照をサポートしているが、名前付きブロックや関数内のローカル変数への参照はサポートされていない。 ドット記述の前に配列位置参照は定数である必要がある。例えば a[2].bは参照することが出来るが、a[x].bは参照できない。

Generate文などで生成されたインスタンスVerilog標準の形式に変換されて参照することが出来る。例えば、Verilog{cellName}[{instanceNumber}]と記述すると {cellname}__BRA__{instanceNumber}__KET__ という形式に変換される。

VerilatorによるGenerate文はgenblkキーワードに、番号をつけた形で生成される。 このキーワードは、他のシミュレータでは異なるかもしれないが、Verilog仕様ではこの数値を規定してはいないので、特に問題はない。

浮動小数

Verilatorでは、浮動小数点表現(real)をサポートしない。

ラッチ

Verilatorはエッジセンシティブのデザインに最適化されており、ラッチ方式も実行することが出来るが、ラッチ記述の周りでは、最適化は抑止される。

構造体と共用体

Verilatorはpacked structpacked unionのみサポートしている。メンバ変数のrandおよびrandc記述は無視される。 全ての構造体と共用体は単純なベクタに変換される。 従って、メンバ内でブロッキング代入とノンブロッキング代入を併用することはサポートされていない。

時間

全ての遅延記述 (#) は無視される。

Unknownステート

Verilatorは2状態のみサポートしているシミュレータのため、4状態はサポートしていない。 従って、初期値の問題がしばしば発生する。

比較演算子(===, !==)は標準的な(==/!=)に変換される。 4状態のシミュレータでは、===による'X'の比較はFalseとなるが、Verilatorでは動作が異なる。

不定値'X'をassignした場合、実際にはランダムな値がassignされることになる(この値は--x-assignスイッチで変更できる)。 従って、この値が実際に後続の論理で使用されるとエラーを引き起こす可能性がある。 Verilog 2001では整数型の初期値はゼロであるが、Verilatorではランダムな値が設定される。

functionを使っている場合、全ての変数はランダム値が設定される。

いくつかのランダム値のシミュレーションを行うことで、リセット処理が正しく行われているかどうかを確認することが出来る。

最初の実行では、関数は変数をゼロに設定し、2回目では、初期値を1に設定する。3回目以降の実行では、初期値がランダムに設定する。 全ての結果が一致すれば、リセットは正しく動作していることが確認できる。

Tri/Inout

Verilatorは単純な構造を2ステートに変換する。 pullup, pulldown, buff0, buff1, notif0, notif1, pmos, nmos, tri0, tri1はサポートされている。

関数&タスク

全ての関数およびタスクはインライン化される(C言語に変換された際、関数化はされない)。

再帰関数および再帰タスクはサポートされない。 全ての入力および出力はautomaticとして宣言される。

クロック

Verilatorはクロックを正確に処理しようとするが、スケジューリングアルゴリズムと性能最適化のために問題を発生させる可能性がある。 もっとも安全なオプションは、モデルに入力される全てのクロックおよびwireは、主要な入力にattachされることである。 また、クロックを正しく動作させるためには、/* verilator clock_enable */ 属性を付加すること。

Big-Endianの範囲

ビット範囲の記述は、MSBがLSB以上になるように記述しなければならない。 リトルエンディアン[0:15]C++と互換性を保つのが難しいためサポートされていない。

ゲートプリミティブ

2状態のゲートプリミティブ(and, buf, nand, nor, not, or, xnor, xor)は単純に変換される。 3状態のMOSゲートはサポートされない。 テーブルはサポートされない。

Specify Block

全てのSpecify Blockおよびタイミングチェックはサポートされない。

配列の初期化

大きな配列を初期化する場合は、non-delayed assign文を使用する必要がある。 Verilatorはこのような記述されている場合警告を発生する。BLKLOOPINITエラーを参照のこと。

配列の領域超過

配列のアドレスを超過して書き込んだ場合、同じ配列内の別の場所が書き込まれるので注意すること。 2のべき乗のサイズの配列では、Verilatorは幅とアドレスの警告を発生する。 2のべき乗でない場合、インデックス0に書き込みが行われる。

配列のアドレスを超過して読み込みが行われた場合、アドレスが2のべき乗にラップされる。 2のべき乗のサイズではない場合、適切な幅の不定な値が返される。

アサーション

Verilatorはアサーションのサポートを始めている。 Verilatorはアサーションif(...) errorのみサポートしており、カバレッジのセクションで記述されているラインカウンタを増加させるためのカバレッジ文をサポートしている。

VerilatorはSEREをまだサポートしていない。全てのアサーションおよびカバレッジ文は、1サイクルで実行できる単純な式である必要がある。

キーワードの制約

以下のキーワードは完全にサポートされている。

`__FILE__, `__LINE__, `begin_keywords, `begin_keywords, `begin_keywords, `begin_keywords, `begin_keywords, `define, `else, `elsif, `end_keywords, `endif, `error, `ifdef, `ifndef, `include, `line, `systemc_ctor, `systemc_dtor, `systemc_header, `systemc_imp_header, `systemc_implementation, `systemc_interface, `timescale, `undef, `verilog

以下のキーワードは一般的にサポートされている。

always, always_comb, always_ff, always_latch, and, assign, begin, buf, byte, case, casex, casez, default, defparam, do-while, else, end, endcase, endfunction, endgenerate, endmodule, endspecify, endtask, final, for, function, generate, genvar, if, initial, inout, input, int, integer, localparam, logic, longint, macromodule, module, nand, negedge, nor, not, or, output, parameter, posedge, reg, scalared, shortint, signed, supply0, supply1, task, time, tri, typedef, var, vectored, while, wire, xnor, xor

++, -- 演算子

独立したインクリメント、デクリメント文の形式でのみサポートされている。

'{} 演算子

一部サポートされない。

cast演算子

単純なスカラ型から、signed, unsigned型の変換しかサポートされない。

chandle型

chandle型は単純にlongintに変換される。

disable

disable文はブロックを無効化するときのみ使用できる。

inside

inside式には、unpacked配列の探索や $ による上位の境界は含まれない。 case insidecase match はサポートされない。

interface

modports周辺でのブロックの生成、virtual interface, unnamed interfaceははサポートされない。

priority if, unique if

単純なif文に変換される。

specify specparam

無視される。

string

Stringは単純な演算でのみサポートされる。Stringによるメソッドの呼び出しはサポートされない。

timeunit, timeprecision

無視される。

uwire

uwireに関する警告は発生しない。通常のwireのように扱われる。

$bits, $countones, $error, $fatal, $finish, $info, $isunknown, $onehot, $onehot0, $readmemb, $readmemh, $signed, $stime, $stop, $time, $unsigned, $warning

一般的にサポートされる。

$display, $write, $fdisplay, $fwrite, $swrite

フォーマット無しで、変数リストのみでprintするような記述はサポートされない。

$displayb, $displayh, $displayo, $writeb, $writeh, $writeo, ...

サポートされない。

$finish, $stop

これらの変数のオプション類はサポートされない。

$fopen, $fclose, $fdisplay, $feof, $fflush, $fgetc, $fgets, $fscanf, $fwrite

PLIのファイル系のコマンドに使用されるものは、ファイルディスクリプタで無ければならない。

$fscanf, $sscanf

整数フォーマットのみサポートされる。

$fullskew, $hold, $nochange, $period, $recovery, $recrem, $removal, $setup, $setuphold, $skew, $timeskew, $width

サポートされない。

$random

シードを設定するためのオプションはサポートされない。

$readmemb, $readmemh

2次元以上の配列に対するreadmemhはサポートされない。

$test$plusargs, $value$plusargs

サポートされているが、これらのタスクを使用するためには、C++/SystemCテストベンチでは、以下を呼び出す必要がある。

    Verilated::commandArgs(argc, argv);

$timeformat

Verilatorではサポートされない。

Verilog-HDLで記述されたオープンソースのハードウェアライブラリ"BaseJump"

HotChips29 で発表された、学生が開発したRISC-V SoC ”Celerity”について資料を読んだ。

news.mynavi.jp

Celerityは学生の作ったRISC-Vのメニーコアチップで、4大学が共同して9か月で作成したチップとなっている。

9か月という短期間で作るための秘訣として、

  • 再利用
  • モジュール化設計
    • 部品レベルで設計を最適化し、ツールの処理時間を短くするため階層化設計を行った。
    • 詳細な中身を知らなくても利用できるブラックボックス設計を行った。
  • 自動化設計
    • 実装とテストのフローを抽象化して、色々な設計に適用できるようにした。
    • 確認済みのIPコンポーネントを利用し、インテグレーションのテストだけで済むようにした。
    • ハイレベル合成ツールを使用した。
    • アナログ設計が行われていた部分についてもディジタル設計のフローを使うようにした。

ということらしい。

BaseJump: Components for Open Source Hardware

この中で注目したのは、”BaseJump”とよばれるオープンソースのハードウェア部品を使ったということ。

BaseJumpは、オープンソースなハードウェアコンポーネントで誰でも使用することが出来る。 また、ホームページを見てみるとBGAパッケージも開発されており、これらもコンポーネント化されているのだろうか。

System Verilog のハードウェアコンポーネントは、BaseJump STLとして以下のBitbucketのページにまとめられている。

bitbucket.org

中身を見てみると、NoCのようなネットワークルータの部品から、標準的なMuxや非同期FIFOなど、一通りの部品はそろっている。かなり有用そうだ。

オープンソースのハードウェアコンポーネントというと、ParallelaのOH!が有名だが、こちらよりも部品点数は多いのではないだろうか?

github.com

VHDL/Verilogのテスティングフレームワーク"VUnit"

ソフトウェア環境下においてテスティングフレームワークは、例えばJUnit, GoogleTest など。 しかしハードウェア言語向けのテスティングフレームワークというのは少ない。

以前に少し紹介したのは、cocotbというテスティングフレームワーク、こちらはPythonを使用するフレームワークだ。ブログでも少し紹介した。

github.com

msyksphinz.hatenablog.com

それ以外にもう少し発見した。VUnitというVHDL/SystemVerilog用のテスティングフレームワークだ。

VUnit — VUnit documentation

f:id:msyksphinz:20171005010245p:plain

ここでは、Verilogをベースにして調査してみよう。まずはインストールから。

cd vunit
sudo python ./setup.py install

これでVUnitのバイナリがインストールされる。

次にサンプルを眺めてみよう。サンプルコードは、 examples/verilog/uart/に置いてある。

cd vunit/example/verilog/uart
python ./run.py
Compiling ../../../../../../../usr/local/lib/python2.7/dist-packages/vunit_hdl-2.2.1rc0-py2.7.egg/vunit/verilog/vunit_pkg.sv into vunit_lib ...
Compiling src/uart_tx.sv into uart_lib ...
Compiling src/uart_rx.sv into uart_lib ...
Compiling src/test/tb_uart_tx.sv into tb_uart_lib ...
Compiling src/test/tb_uart_rx.sv into tb_uart_lib ...
Starting tb_uart_lib.tb_uart_rx.test_tvalid_low_at_start
pass (P=1 S=0 F=0 T=5) tb_uart_lib.tb_uart_rx.test_tvalid_low_at_start (1.6 seconds)

Starting tb_uart_lib.tb_uart_rx.test_receives_one_byte
pass (P=2 S=0 F=0 T=5) tb_uart_lib.tb_uart_rx.test_receives_one_byte (0.1 seconds)

Starting tb_uart_lib.tb_uart_rx.test_two_bytes_cause_overflow
pass (P=3 S=0 F=0 T=5) tb_uart_lib.tb_uart_rx.test_two_bytes_cause_overflow (0.2 seconds)

Starting tb_uart_lib.tb_uart_tx.test_send_one_byte
pass (P=4 S=0 F=0 T=5) tb_uart_lib.tb_uart_tx.test_send_one_byte (0.2 seconds)

Starting tb_uart_lib.tb_uart_tx.test_send_many_bytes
pass (P=5 S=0 F=0 T=5) tb_uart_lib.tb_uart_tx.test_send_many_bytes (0.2 seconds)

==== Summary ================================================================
pass tb_uart_lib.tb_uart_rx.test_tvalid_low_at_start      (1.6 seconds)
pass tb_uart_lib.tb_uart_rx.test_receives_one_byte        (0.1 seconds)
pass tb_uart_lib.tb_uart_rx.test_two_bytes_cause_overflow (0.2 seconds)
pass tb_uart_lib.tb_uart_tx.test_send_one_byte            (0.2 seconds)
pass tb_uart_lib.tb_uart_tx.test_send_many_bytes          (0.2 seconds)
=============================================================================
pass 5 of 5
=============================================================================
Total time was 2.4 seconds
Elapsed time was 2.7 seconds
=============================================================================
All passed!

こんな調子で実行される。この指定されたrun.pyは何をしているのかということだが、ソースコードに脚注を付けていくと、

from os.path import join, dirname
from vunit.verilog import VUnit

ui = VUnit.from_argv()

src_path = join(dirname(__file__), "src")    # ソースファイルの場所を指定できるようにする。

uart_lib = ui.add_library("uart_lib")              # UART本体を格納するためのライブラリを定義する
uart_lib.add_source_files(join(src_path, "*.sv"))  # ライブラリにソースコードを挿入する

tb_uart_lib = ui.add_library("tb_uart_lib")                   # UARTのテストパタンを格納するためのライブラリを定義する
tb_uart_lib.add_source_files(join(src_path, "test", "*.sv"))  # UARTのライブラリにソースコードを挿入する。

ui.main()  # テスト実行

テストパタンの中身

テストパタンの中身をチェックしてみる。例えば、 examples/verilog/uart/src/test/tb_uart_tx.sv を見てみよう。いくつかのtaskが定義されていることが分かる。

テストケースとテストスイート

TEST_SUITEはテストスイートを定義する。TEST_SUITEの中には、テストケースを定義している。テストケースは、テストスイート内に複数定義することが出来る。

テストケースはそのままテストを記述している。

   `TEST_SUITE begin
      `TEST_CASE("test_send_one_byte") begin
         send();
         check_all_was_received();
      end
      `TEST_CASE("test_send_many_bytes") begin
         for (int i=0; i<7; i++) begin
            send();
         end
         check_all_was_received();
      end
   end

テストケースでは、いくつかのdefineが使用できる。

  • TEST_SUITE_SETUP : 全てのテストスイート共通のセットアップを行う。
  • TEST_CASE_SETUP : 全てのテストケースの共通のセットアップを行う。
  • CHECK_EQUAL : アサーションを記述する。
  • TEST_CASE_CLEANUP : 全てのテストケース共通で、テスト終了後の処理を行う。
  • TEST_SUITE_CLEANUP : 全てのテストスイート共通で、テスト終了後の処理を行う。
  • WATCHDOG : タイムアウト設定

これでいくつかサンプル作って試してみたいな。

VerilogのSpecifyブロックまとめ

ちょっとVerilogのSpecify記述をいろいろチェックする機会があったので、ここでまとめておく。

VerilogのSpecifyブロック内では、主にセットアップ時間、ホールド時間などを記述するが、それ以外にもさまざまな制約条件などを指定することが出来る。

specify
  // タイミング制約事項など記述する。
  $setup (posedge clk, ...);
endspecify

specifyブロックには、以下のような制約チェックタスクを記述することが出来る。

以下の記事を参考にした。

verilog.renerta.com

  • $setup (data_event, reference_event, limit[, notifier]) ;
  • $skew (reference_event, data_event, limit[,notifier]) ;
  • $hold (reference_event, data_event, limit[,notifier]) ;
  • $recovery (reference_event, data_event, limit, [notifier]) ;
  • $setuphold (reference_event, data_event, setup_limit, hold_limit, [notifier]) ;
  • $width (reference_event, limit, threshold [,notifier]) ;
  • $period (reference_event, limit[,notifier]) ;
  • $nochange (reference_event, data_event, start_edge_offset, end_edge_offset [,notifier]) ;

setup

セットアップタイムの制約を記述する。データのFFへの到着がリファレンスイベントのある一定時間よりも前に完了しておく必要があるというもの。 以下の図において、赤の期間にデータを変化させることは禁止となる。

f:id:msyksphinz:20170313231252p:plain

以下の条件を満たした場合、違反となる。

(time of reference event) - (time of data event) < limit

skew (reference_event, data_event, limit[,notifier]) ;

以下の条件を満たした場合、違反となる。

(time of data event) - (time of reference event) > limit

クロック間のタイミング調整についての制約となる。あるデータのエッジに対して、それを受け取るクロックのエッジがずれている場合に違反となる。

hold (reference_event, data_event, limit[,notifier]) ;

以下の条件を満たした場合、違反となる。

(time of data event) - (time of reference event) < limit

リファレンスのクロックに対して、データの到着が早すぎる場合、hold違反となる。通常これはFFの付きぬけを防止するためのもので、hold違反となった場合はバッファなどを挿入して遅延を挿入する。

以下の図において、赤の期間にデータを変化させることは禁止となる。

f:id:msyksphinz:20170313231340p:plain

recovery (reference_event, data_event, limit, [notifier]) ;

以下の条件を満たす場合に違反となる。等式としてはholdと同一だが、使い分けがあるのだろうか?また、reference eventはエッジトリガでなければならない。

(time of data event) - (time of reference event) < limit

setuphold (reference_event, data_event, setup_limit, hold_limit, [notifier]) ;

setupタイムとholdタイムを同時にチェックする。

width (reference_event, limit, threshold [,notifier]) ;

リファレンスイベントの間隔をチェックする。以下を条件を満たした場合、違反となる。data eventは明示されていないが、デフォルトでは、reference eventの逆エッジとなる。 つまり、あるエッジから逆エッジまでの期間が、threasholdよりも大きく、limitよりも小さい場合に、違反となる。

threshold < (time of data event) - (time of reference event) < limit

period (reference_event, limit[,notifier]) ;

等式としてはrecovery, holdと同一である。ある信号が十分に長いかどうかをチェックしている。

(time of data event) - (time of reference event) < limit

nochange (reference_event, data_event, start_edge_offset, end_edge_offset [,notifier]) ;

data_eventが、start_edge_offsetから、end_edge_offsetの間に変化した場合に、違反がレポートされる。

EmacsのExuberant Ctags導入試行(Verilog-HDLで使いものになるか?)

普段の開発には主にEmacsを使っているのだが、良くある事例として現在参照しているモジュールがどこにあるのか分からなかったり、この定数がどこから定義されているのか分からなかったりすることが良くありますよね。

ソフトウェアの世界だと、これらの関数定義にジャンプするためには、WindowsではVisual StudioのIntelliSenseが有名だが(僕も良くお世話になっている)、Emacs/Vimの世界ではctags/etagsというものがあるらしい。

ctags.sourceforge.net

これは現在のソースコードを解析して、関数の定義場所にジャンプしてくれる優れ物らしい。さっそく試してみよう。

Ubuntuでの導入手順

Ubuntuで導入するためには、普通にaptitudeするだけで良い。

sudo aptitude install exuberant-ctags

次に、ファイルの存在する場所でexuberant-ctagsを実行する。TAGSファイルが作成された。

$ etags `find . -name *.v` `find . -name *.vh`
$ ls -ltr
total 600
drwxrwxr-x  3 vagrant vagrant   4096 Nov 14 14:54 ip
drwxrwxr-x  5 vagrant vagrant   4096 Nov 14 14:54 common
drwxrwxr-x 13 vagrant vagrant   4096 Nov 14 14:54 mag_core
drwxrwxr-x 10 vagrant vagrant   4096 Nov 14 14:54 cocotb
drwxrwxr-x  3 vagrant vagrant   4096 Nov 14 14:59 tests
-rw-rw-r--  1 vagrant vagrant 592799 Dec 26 16:54 TAGS

.emacs/init.elの編集

emacsでのキーバインドを作成して、使いやすいようにしておく。以下のサイトを参考にした。

mugijiru.seesaa.net

;;
;; CTAGS
;;
(defun find-tag-next ()
  (interactive)
  (find-tag last-tag t))
(global-set-key (kbd "M-g .")   'find-tag-regexp)
(global-set-key (kbd "M->")     'find-tag-next)
(global-set-key (kbd "M-,")     'find-tag-other-window)
(global-set-key (kbd "M-g M-.") 'anything-c-etags-select)

CTagsの試行

例えば、Verilogファイルを開いていて、以下のようにモジュールのインスタンシエーション部にカーソルを当ててM-.を実行した。ここでは、mag_ifuというモジュールがどこに定義してあるのか探したい。

f:id:msyksphinz:20161227020016p:plain

そういえばまだTAGファイルを指定してなかったので、TAGファイルの場所を指定する。

f:id:msyksphinz:20161227020106p:plain

実行した。ところが、違うファイルが参照されてしまった。

f:id:msyksphinz:20161227020154p:plain

M->を何回か入力して、当該ファイルに飛んだ。どうやら、サーチには一工夫必要らしい。

f:id:msyksphinz:20161227020333p:plain

これ、最近流行りのVisual Studio Codeなどではどうなのかな?ちょっと試してみよう。

格安のVerillogシミュレータ Veritak をコマンドラインから起動する方法

現在、趣味でのVerilog開発は、個人でも格安で使用可能なVerilogシミュレータであるVeritakを使用している。

japanese.sugawara-systems.com

Verilog-HDLを使用したシミュレータとしては、有料のものを含め以下のものが挙げられるのではないだろうか。簡単に一覧にしてみた。

名前 開発元 有料/無料
VCS Synopsys 有料 Big-3
NC-Verilog Cadence 有料 Big-3
QuestaSim Mentor 有料 Big-3
Vivado Simulator Xilinx 無料 Vivadoと統合されている
Veritak Sugawara-Systems 有料 Big-3と比較すると安価
Cver OSS 無料
Icarus Verilog OSS 無料
Verilator OSS 無料 2値のみサポート、高速

VeritakはSugawara-Systemsが開発した格安のシミュレータで、個人でも利用できる値段ながら、高速なシミュレーションと波形デバッグが可能なシミュレータだ。 僕も10年来このシミュレータを利用させて頂いている。 購入したのが学生のときで、5000円で高速シミュレータを手に入れられるというのはまさに夢のようなツールだった。 (最近更新が無いのでどうなっているのか気になっているが...)

さて、このVeritakだが、公式サイトを見て分かるように、GUIによるシミュレーションが基本となっている。 私も普段使用するときは、GUIで波形を取得しながらシミュレーションするのが一般的だ。

f:id:msyksphinz:20160923015033p:plain

シミュレーションとデバッグの手順としては、

  1. シミュレーション時にテキストログを出す
  2. 検証スクリプトを使ってログの正当性を確認
  3. ログに間違いが含まれていれば、取得したGUIで確認
  4. 1.に戻ってシミュレーション

となる。デバッグ中はこれで良いのだが、リグレッションなどのフェーズになるとGUIは不要となり、なるべくならばコマンドラインで実行したい。

これを実現するためには、Veritakシミュレーション時に生成されるveritak_command.txtを参考にすれば良い

veritak_command.txt を使用してコマンドラインからシミュレーションする方法を確立する

veritak_command.txtを確認すると、以下のような、シミュレーションを実行するためのコマンドのようなものが格納されていた。

VERITAK2 --t --C0 --M3 --w --a --ST --W "C:\msys64\home\masay\work\magnetor-1\hardware\mag_core\mag_top\veritak"  --D "FILENAME=\"coremark.riscv.hex\"" --D "SIMULATION" --i "../../../common/include" "../../../../hardware/mag_core/mag_top/sim/tb_mag_top.v" "../../../../hardware/mag_core/mag_top/sim/inst_logger.sv" "../../../../hardware/mag_core/mag_top/sim/pipe_logger.sv" "../../../../hardware/mag_core/mag_rnu/rtl/mag_rnu.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_freelist.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_future_file.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_phy_rfile.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_rename_map.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_controller.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_operand_slicer.v" "../../../../hardware/mag_core/mag_rnu/rtl/csu_main.v"
...

これをそのままコマンドライン上に貼り付けて、実行してみる。

$ veritak2.exe ...

compile done with 0 errors.

そのまま実行しても、コマンドラインモードでは実行が完了しなかった。どうやらシミュレーションが指定の時間に到達しても、実行は終了しないらしい。 以下のページに解説があったので、そのまま実行してみる。つまり、先頭に付属しているオプションを取り払って、必要なものだけを挿入する。

http://japanese.sugawara-systems.com/tutorial/veritak/newpage34.htm

/c/Users/masay/Downloads/veritakwinB383A/VERITAK2 --W "C:\msys64\home\masay\work\magnetor-1\hardware\mag_core\mag_top\veritak"  --D "FILENAME=\"coremark.riscv.hex\"" --D "SIMULATION" --i "../../../common/include" --n --G "../../../../hardware/mag_core/mag_top/sim/tb_mag_top.v" "../../../../hardware/mag_core/mag_top/sim/inst_logger.sv" "../../../../hardware/mag_core/mag_top/sim/pipe_logger.sv" "../../../../hardware/mag_core/mag_rnu/rtl/mag_rnu.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_freelist.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_future_file.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_phy_rfile.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_rename_map.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_controller.v" "../../../../hardware/mag_core/mag_rnu/rtl/rnu_operand_slicer.v" "../../../../hardware/mag_core/mag_rnu/rtl/csu_main.v" "../../../../hardware/mag_core/mag_ifu/rtl/mag_ifu.v" "../../../../hardware/mag_core/mag_ifu/rtl/mag_inst_buf.v" "../../../../hardware/mag_core/mag_ifu/rtl/ifu_state.v"
...

追加したのは、--n --Gオプションだ。これらが何者なのかは解説は無かったが、重要なオプションであることは間違いない。

コマンドライン実行と、GUI実行の所要時間を取得する

GUIモードとCUIモードで実行時間を比較してみた。

GUIモード CUIモード
2:28 2:05

まあ、CUIの方がちょっとだけ速いかな。