##はじめに。
cocotbは、Pythonを使用したHDLテストフレームワークです。
しばらく成り行きを見守っていたのですが、バージョン1.0がリリースされたので試してみることにしました。
##cocotbの特徴。
cocotbは、Potential Ventures社が開発しているHDL向けの軽量なテストフレームワークで、Pythonで記述されています。BSDライセンスにてGitHubに公開されています。
https://github.com/potentialventures/cocotb
ドキュメントはこちらになります。
http://cocotb.readthedocs.org/en/latest/index.html
FPGA開発における速やかな検証環境の立ち上げ意識しており、SystemVerilogで書かれたUVMよりも非常に軽く、またPythonで書かれているため、簡素で見やすい記述になっています。
対応シミュレーターは、Icarus Verilog, VCS, Riviera-PRO, Questa(ModelSim), Incisiveに対応しています。
特にIcarusとVCSは親和性が良さそうです。
また、xUnit用のファイル出力機能を備えており、CIツールであるJenkinsと連携しやすいのも特徴と言えます。
DUT(HDL)とのアクセスにはPythonのジェネレータとコルーチン、デコレータ技術が使われています。コルーチンでシミュレーターをポーズさせ、cocotbの処理を入れて、シミュレーターを再開させる仕組みです。
Pythonを使っている人にも慣れないところがありますが、cocotbでは使いどころがわかればあまり意識をする必要はありません。
今回はサンプルとドキュメントを手かがりに、Verilogの順序回路を検証する環境をcocotbを使って記述してみました。OSは、CentOS6.6です。
##準備
GitHubよりダウンロードします。
git clone https://github.com/potentialventures/cocotb
今回はexample
ディレクトリに新たにワークディレクトリを作成し、そこにrtl
およびtests
ディレクトリを作成し、それぞれDUT、テストを保存しました。
シミュレータにはIcarus Verilogを使用します。makefiles
ディレクトリにある「Makefile.sim」のシミュレーター選択部分に「icarus」を入れます。
# Default to Icarus if no simulator is defined
SIM ?= icarus
##DUT
8bitの順序回路になります。
中で、波形取得のためにvcdファイルを出力させるようにしています。
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からの出力値をチェックするシナリオです。
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スクリプト名を入力します。
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で初期化されて、その後ランダムな値が入力されることがわかります。
##おわりに。
Pythonの知識がある程度あると、比較的簡単に検証環境を構築できる印象です。
しかし、自分の理解不足なところもあるかもしれませんが、ドキュメントはあまり充実しているとは言えません。また、Tutorialの最初にあるEndian Swapperは敷居が高すぎるように感じました。
とはいえ、SystemVerilogではデバッグしづらいテスト部分がPythonだとかなり分かり易く、また今回のrandomのようにPythonのライブラリが使えるのは、コストや利用者の多さから考えて利点が大きいと思っています。
まだ1.0が出たばかりでもあり、今後に期待です。