VerilogのテストにPython製フレームワーク「cocotb」を使う。

##はじめに。
cocotbは、Pythonを使用したHDLテストフレームワークです。
しばらく成り行きを見守っていたのですが、バージョン1.0がリリースされたので試してみることにしました。

##cocotbの特徴。
cocotbは、[Potential Ventures社](http://potential.ventures/)が開発しているHDL向けの軽量なテストフレームワークで、Pythonで記述されています。BSDライセンスにてGitHubに公開されています。
https://github.com/potentialventures/cocotb

ドキュメントはこちらになります。
http://cocotb.readthedocs.org/en/latest/index.html

FPGA開発における速やかな検証環境の立ち上げ意識しており、SystemVerilogで書かれた[UVM](http://accellera.org/downloads/standards/uvm)よりも非常に軽く、またPythonで書かれているため、簡素で見やすい記述になっています。

対応シミュレーターは、[Icarus Verilog](http://iverilog.icarus.com/), [VCS](http://www.synopsys.com/jp2/Tools/Verification/FunctionalVerification/Pages/VCS.aspx), [Riviera-PRO](https://www.aldec.com/jp/products/functional_verification/riviera-pro), [Questa(ModelSim)](http://www.mentorg.co.jp/products/fv/questa/), [Incisive](http://www.cadence.com/products/fv/enterprise_simulator/pages/default.aspx)に対応しています。
特にIcarusとVCSは親和性が良さそうです。

また、xUnit用のファイル出力機能を備えており、CIツールである[Jenkins](http://jenkins-ci.org/)と連携しやすいのも特徴と言えます。

DUT(HDL)とのアクセスにはPythonのジェネレータとコルーチン、デコレータ技術が使われています。コルーチンでシミュレーターをポーズさせ、cocotbの処理を入れて、シミュレーターを再開させる仕組みです。
Pythonを使っている人にも慣れないところがありますが、cocotbでは使いどころがわかればあまり意識をする必要はありません。

今回はサンプルとドキュメントを手かがりに、Verilogの順序回路を検証する環境をcocotbを使って記述してみました。OSは、CentOS6.6です。

##準備
GitHubよりダウンロードします。

```sh
git clone https://github.com/potentialventures/cocotb
```

今回は`example`ディレクトリに新たにワークディレクトリを作成し、そこに`rtl`および`tests`ディレクトリを作成し、それぞれDUT、テストを保存しました。

シミュレータにはIcarus Verilogを使用します。`makefiles`ディレクトリにある「Makefile.sim」のシミュレーター選択部分に「icarus」を入れます。

```mf
# Default to Icarus if no simulator is defined
SIM ?= icarus
```

##DUT
8bitの順序回路になります。
中で、波形取得のためにvcdファイルを出力させるようにしています。

```verilog:dff.v

module dff(
  input            RST_N,
  input            CLK,
  input      [7:0] D,
  output reg [7:0] Q
);

  always @(negedge RST_N, posedge CLK)
    if(~RST_N)
      Q <= 8'h0;
    else
      Q <= D;

  initial begin
    $dumpfile("dump.vcd");
    $dumpvars(1, dff);
  end

endmodule
```

##テスト
ファイル1個で済ますため、テストベンチ環境(DUTとのインスタンス)と簡単なドライバ・チェッカを入れました。
内容としてはランダムな値を入力し、1サイクル後DUTからの出力値をチェックするシナリオです。

```py:tests.py
import cocotb
from cocotb.triggers import Timer, RisingEdge
from cocotb.result import TestFailure
from cocotb.clock import Clock

import random

class DffTB(object):
    def __init__(self, dut, dubug=True):
        self.dut = dut

    @cocotb.coroutine
    def reset(self, duration=10000):
        self.dut.log.info("Resetting DUT")
        self.dut.RST_N <= 0
        self.dut.D <= 0
        yield Timer(duration)
        yield RisingEdge(self.dut.CLK)
        self.dut.RST_N <= 1
        self.dut.log.info("Out of reset")

    @cocotb.coroutine
    def gen_and_check(self):
        D = random.randint(0, 255)
        self.dut.D = D;
        yield RisingEdge(self.dut.CLK)
        yield Timer(1)
        if int(self.dut.Q) != D :
            raise TestFailure(
                "[NG] Compre error. D==%s Q==%s" %  (D, int(self.dut.Q)))
        else :
            self.dut.log.info("[OK]")


@cocotb.coroutine
def clock_gen(signal):
    while True:
        signal <= 0
        yield Timer(5000)
        signal <= 1
        yield Timer(5000)

@cocotb.test()
def basic_test(dut):
    """basic_test"""
    tb = DffTB(dut)
    cocotb.fork(clock_gen(dut.CLK))
    yield RisingEdge(dut.CLK)
    yield tb.reset()

    for i in range(30):
        yield tb.gen_and_check()
```

cocotbでは「dut」が予約語で、これがDUTのトップ階層と対応します。
ここでは簡単にするため、classのコンストラクタは今のところdutのインスタンスしか記述されていませんが、他の検証モジュールやユーティリティを初期化等の処理を入れていきます。

テストシナリオは、最後に書かれているようにcocotb.test()をデコレートして記述します。

シミュレーションを行うに当たって、主な処理する部分はcocotb.coroutineをデコレートします。上記記述では「clock_gen」や「gen_and_check」辺りがそれになります。

##シミュレーション実行スクリプト
`tests`ディレクトリにMakefileを用意しました。
(TOPLEVEL)にDUTトップ階層を指定し、(MODULE)にPythonスクリプト名を入力します。

```mf:Makefile
TOPLEVEL := dff
TOPLEVEL_LANG ?= verilog

PWD=$(shell pwd)
COCOTB=$(PWD)/../../..

ifeq ($(OS),Msys)
WPWD=$(shell sh -c 'pwd -W')
PYTHONPATH := $(WPWD)/../model;$(PYTHONPATH)
else
WPWD=$(shell pwd)
PYTHONPATH := $(WPWD)/../model:$(PYTHONPATH)
endif
export PYTHONPATH

VERILOG_SOURCES = $(WPWD)/../rtl/dff.v
GPI_IMPL := vpi

export TOPLEVEL_LANG

MODULE ?= tests

include $(COCOTB)/makefiles/Makefile.inc
include $(COCOTB)/makefiles/Makefile.sim
```

##実行結果・波形
以下のように、初期化フェイズからリセット発行、出力値チェック、シミュレーション終了まで流すことが出来ました。

```
        TESTCASE= TOPLEVEL=dff \
        vvp -M /tmp/cocotb/build/libs/x86_64 -m gpivpi sim_build/sim.vvp   
     -.--ns INFO     cocotb.gpi                                GpiCommon.cpp:47   in gpi_print_registered_impl       VPI registered
     0.00ns INFO     cocotb.gpi                                  gpi_embed.c:229  in embed_sim_init                  Running on Icarus Verilog version 0.9.6 
     0.00ns INFO     cocotb.gpi                                  gpi_embed.c:230  in embed_sim_init                  Python interpreter initialised and cocotb loaded!
     0.00ns INFO     cocotb.gpi                                  __init__.py:103  in _initialise_testbench           Running tests with Cocotb v1.0 from /tmp/cocotb
     0.00ns INFO     cocotb.gpi                                  __init__.py:119  in _initialise_testbench           Seeding Python random module with 1430897996
     0.00ns INFO     cocotb.regression                         regression.py:153  in initialise                      Found test tests.basic_test
     0.00ns INFO     cocotb.regression                         regression.py:254  in execute                         Running test 1/1: basic_test
     0.00ns INFO     ..routine.basic_test.0x7f2a3156ffd0       decorators.py:186  in send                            Starting test: "basic_test"
                                                                                                                               Description: basic_test
VCD info: dumpfile dump.vcd opened for output.
     5.00ns INFO     cocotb.dff                                     tests.py:14   in reset                           Resetting DUT
    15.00ns INFO     cocotb.dff                                     tests.py:20   in reset                           Out of reset
    25.00ns INFO     cocotb.dff                                     tests.py:32   in gen_and_check                   [OK]
    35.00ns INFO     cocotb.dff                                     tests.py:32   in gen_and_check                   [OK]

    (中略)

   315.00ns INFO     cocotb.dff                                     tests.py:32   in gen_and_check                   [OK]
   315.00ns INFO     cocotb.regression                         regression.py:201  in handle_result                   Test Passed: basic_test
   315.00ns INFO     cocotb.regression                         regression.py:162  in tear_down                       Passed 1 tests (0 skipped)
   315.00ns INFO     cocotb.regression                         regression.py:168  in tear_down                       Shutting down...
```

波形は以下のようになりました。RST_Nで初期化されて、その後ランダムな値が入力されることがわかります。

![waves.png](https://qiita-image-store.s3.amazonaws.com/0/32925/5363f4ca-a975-0501-466c-968b817f068c.png)

##おわりに。
Pythonの知識がある程度あると、比較的簡単に検証環境を構築できる印象です。
しかし、自分の理解不足なところもあるかもしれませんが、ドキュメントはあまり充実しているとは言えません。また、Tutorialの最初にあるEndian Swapperは敷居が高すぎるように感じました。
とはいえ、SystemVerilogではデバッグしづらいテスト部分がPythonだとかなり分かり易く、また今回のrandomのようにPythonのライブラリが使えるのは、コストや利用者の多さから考えて利点が大きいと思っています。
まだ1.0が出たばかりでもあり、今後に期待です。