6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

UVMでDUTへのアクセス方法をまとめる

Last updated at Posted at 2020-05-02

初めての投稿になります!
基本Qiitaは読んでばかりなのですが、たまには書く方もしてみようかなと、ふと思い立った次第です。
というかHDL系の話題ってQiitaではあんまり多くないですよね。
やっている人が単純に少ないのか、あまりオープンな文化じゃないのか、真実の程は定かではありませんが・・・逆に言えば、まだまだ貢献の余地が残っているということです。
僕自身、エンジニアとしては未熟ですが、誰かのお役に立てれば光栄です。間違っていたらご指摘くださいね。

「動的な世界」と「静的な世界」を結びつける

SystemVerilogでは、Verilog HDLからの機能拡張として、C++のようなクラスの概念が導入されました。クラスを使うことのメリットは、テスト環境を動的に構築できることにあります。テストの開始から終了の間に、クラスの生成や多態性を駆使することによって、テスト環境を変えることができるのです。
一方で、そもそもHDLはデジタル回路をデザインするための言語なので、テスト対象のデザイン(= DUT、Design Under Test)は静的な要素になります。ASICやFPGAの動作中に、回路が動的に生成されたら怖いですよね。そのトランジスタはどこからやってきたのって感じです笑

ここで疑問が生じます。クラスが持たらした「動的な世界」、DUTが存在する「静的な世界」、これらをどう結びつければいいのでしょうか。以下の論文には、検証のプロによる回答がきれいにまとまっています。

Daviid Rich著:The Missing Link: The Testbench to DUT Connection

論文を読みたい方は、みんな大好きVerification Academyにあります。論文によると、以下の2つの方法があるようです。

  • 仮想インタフェース(Virtual Interface)を使う
  • 抽象クラス(Abstract Class)を使う

本稿では、UVMによる記述例を使って、この2つの方法を紹介したいと思います。

仮想インタフェース(Virtual Interface)

テスト用クラスのプロパティに仮想インタフェースを用意し、仮想インタフェースを通してDUTにアクセスする方法です。一からテスト環境を構築する場合、普通はこちらを使うのではないでしょうか。UVMのチュートリアルをやっていると、必ず仮想インタフェースの節が存在するほどです。
VIF.png
それでは、ソースコードの紹介に入ります。ダミープロジェクトということで、接頭語にdummybusをつけることにします。
まずはDUTがないと始まりません。4kByteのシングルポートRAMにします。

ram.v
module ram (
    input wire clk,
    input wire [31:0] w_data,
    input wire [11:0] addr,
    input wire write,
    output wire [31:0] r_data
);
    reg [31:0] ram [1023:0];
    reg [31:0] addr_q;
    always @ (posedge clk)
    begin
        if (write)
            ram[addr] <= w_data;
        addr_q <= addr;
    end
    assign r_data = ram[addr_q];
endmodule

次にインタフェースを定義します。DUTのポートをそのまま持ってきます。

dummybus_if.sv
interface dummybus_if (input clk);
    logic [11:0] addr;
    logic [31:0] w_data;
    logic [31:0] r_data;
    logic write;
endinterface: dummybus_if

DUTへのトランザクションを定義します。アドレスとデータ、Read/Writeがあれば十分でしょうか。

dummybus_item.svh
typedef enum bit {READ, WRITE} direction_e;
class dummybus_item extends uvm_sequence_item;
    `uvm_object_utils(dummybus_item)
    bit [9:0] addr;
    bit [31:0] data;
    direction_e direction;

    function new(string name = "dummybus_item");
        super.new(name);
    endfunction: new

    function void set_transaction (
        bit [9:0] addr, 
        bit [31:0] data, 
        direction_e direction);
        this.addr = addr;
        this.data = data;
        this.direction = direction;
    endfunction: set_transaction
endclass: dummybus_item

トランザクションを信号に変換するためのクラスを作ります。先ほど作成したインタフェースを、仮想(virtual)インタフェースとしてプロパティに設定します。仮想インタフェースを経由して、DUTへの信号をドライブ、あるいはDUTからの信号をモニタします。

dummybus_vif_driver.svh
class dummybus_vif_driver extends uvm_driver # (dummybus_item);
    virtual dummybus_if vif;
    `uvm_component_utils(dummybus_vif_driver);

    function new(string name , uvm_component parent);
        super.new(name, parent);
    endfunction: new

    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        if (!uvm_config_db#(virtual dummybus_if)::get(this, get_full_name(), "vif", vif))
            `uvm_error("NO_VIF", "Failed to getting virtual interface")
    endfunction: connect_phase

    task run_phase(uvm_phase phase);
        forever begin
            seq_item_port.get_next_item(req);
            repeat (2) @(posedge vif.clk);
            if (req.direction == READ) begin
                vif.addr = req.addr;
                vif.write = 1'b0;
                repeat (2) @(posedge vif.clk);
                $display("[VIF] time:%0t, Read %0d from the address %0d",$time, vif.r_data, vif.addr);
            end
            else begin
                vif.addr = req.addr;
                vif.w_data = req.data;
                vif.write = 1'b1;
                repeat (2) @(posedge vif.clk);
                $display("[VIF] time:%0t, Write %0d in the address %0d",$time, vif.w_data, vif.addr);
            end
            seq_item_port.item_done(req);
        end
    endtask: run_phase
endclass: dummybus_vif_driver

後はテストベンチにて、DUTとインタフェースを配置、インタフェースと仮想インタフェースを結びます。
これにて仮想インタフェースでDUTにアクセスする仕組みの完成です!
動作するソースコードはまとめて最後に紹介します。

抽象クラス(Abstract Class)

テスト用クラスのプロパティとして抽象クラスを用意し、抽象クラスを通してDUTにアクセスする方法です。既存の検証用資産を流用したい場合、こちらの方法を使って解決することができます。というか、僕が本稿を書いているモチベーションはここにあります。この方法を知るまでに、Verification Academyの質問フォームをどれだけ辿ったことか・・・。
利用シーンの例を紹介します。BFM(Bus Function Model)がVerilog HDLによるモジュールとして既にあります。モジュールは静的な要素であるため、クラスのプロパティとして持つことはできません。かといって、BFMをクラス形式に書き換えるのは面倒です。そもそも、ベンダが提供するBFMだと手の付けようがありません。さて、この資産を活用するにはどうしたらいいでしょうか。
オブジェクト指向言語を勉強した人なら、Adapterパターンという単語を目にしたことがあると思います。「新しいクラス」と「既存クラス」のインタフェースに不整合があるとき、「新しいクラス」から「既存クラス」を扱うためのアダプタを作ろう、というやつです。「既存クラス」を弄らないことがポイントです。このベストプラクティスに従って、上記問題を解決します。
Aclass.png
先ほどのRAM(ram.v)に対し、以下のようなBFMが既にあるものとします。タスクの中身は面倒なので省略しています。

dummybus_bfm.v
module dummybus_bfm(
    input clk
);
    task send_transaction(
        input [11:0] addr, input [31:0] data, input direction
    );
    begin
        @(posedge clk);
        if (direction == 1'b0)
            $display("[BFM] time:%0t, Read a value from the address %0d",$time, addr);
        else
            $display("[BFM] time:%0t, Write %0d in the address %0d",$time, data, addr);
    end
    endtask
endmodule

BFMに対応した抽象クラスを作成します。クラス定義にvirutal修飾子、ファンクション・タスク定義にはpure virtual修飾子を付けます。

dummybus_abstract.svh
virtual class dummybus_abstract;
    pure virtual task send_transaction(bit[11:0] addr, bit[31:0] data, bit direction);
endclass: dummybus_abstract

トランザクション(dummybus_item.svh)を抽象クラスで扱える形式に変換するクラスを作成します。トランザクションの中身に応じて、抽象クラスの持つファンクションやタスクをコールします。

dummybus_bfm_driver.svh
class dummybus_bfm_driver extends uvm_driver # (dummybus_item);
    dummybus_abstract bfm_adapt;
    `uvm_component_utils(dummybus_bfm_driver);

    function new(string name , uvm_component parent);
        super.new(name, parent);
    endfunction: new

    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        if (!uvm_config_db#(dummybus_abstract)::get(this, get_full_name(), "bfm_adapt", bfm_adapt))
            `uvm_error("NO_BFM", "Failed to getting concrete class")
    endfunction: connect_phase

    task run_phase(uvm_phase phase);
        forever begin
            seq_item_port.get_next_item(req);
            bfm_adapt.send_transaction(req.addr, req.data, req.direction);
            seq_item_port.item_done(req);
        end
    endtask: run_phase
endclass: dummybus_bfm_driver

抽象クラスを派生し、具象クラスを作成します。ここでBFMにアクセスしています。

dummybus_concrete.svh
`define BFM $root.tb.bfm
class dummybus_concrete extends dummybus_abstract;
    task send_transaction(bit[11:0] addr, bit[31:0] data, bit direction);
        `BFM.send_transaction(addr, data, direction);
    endtask;
endclass: dummybus_concrete

ここで注意したいのは、具象クラスからBFMにアクセスする際、$rootから階層的にインスタンスツリーを辿っていることです。つまり、具象クラスはテストベンチの構造ありきで作っています。当然のことながら、テストベンチが変わると、具象クラスも変える必要があります。
UVMでは、テスト用のクラス一式をパッケージにまとめることが推奨されていますが、具象クラスはパッケージに入れません(というか、パッケージを使う理由が、$root文を弾くため、という記述を見た記憶があります)。テストベンチに直接配置またはインクルードして使います。

後はテストベンチにて、DUTとBFMを配置し、具象クラスを実体、具象クラスと抽象クラスを結びます。
これにて、抽象クラスでDUTにアクセスする仕組みの完成です!
動作するソースコードはまとめて最後に紹介します。

まとめ、その他コード一式

テスト用クラスからDUTにアクセスするための代表的な方法を紹介しました。
個人的な意見としては、特に制約がなければ仮想インタフェースがお勧めです。
既にBFMがある場合は、BFMを書き直すのではなく、仮想クラスを使いましょう。

最後に、僕が動作確認したコードを紹介します。既に紹介したコードは再掲しません。シミュレータはQuartus Prime Lite Edition 18.1についてきたModelsim Intel Starter Edition 10.5bを使っています。無料なので、お金を積まなくても試せると思います。
・パッケージ

dummybus_pkg.sv
package dummybus_pkg;
    import uvm_pkg::*;
    `include "uvm_macros.svh"

    `include "dummybus_abstract.svh"
    `include "dummybus_item.svh"
    `include "dummybus_vif_driver.svh"
    `include "dummybus_bfm_driver.svh"
    `include "dummybus_sequence.svh"
    `include "dummybus_test.svh"
endpackage: dummybus_pkg

・UVMシーケンス

dummybus_sequence.svh
class dummybus_sequence extends uvm_sequence #(dummybus_item);
    `uvm_object_utils(dummybus_sequence);
    dummybus_item item;

    function new(string name = "");
       super.new(name);
    endfunction: new
       
    virtual task body;
        item = dummybus_item::type_id::create("item");
        start_item(item);
        item.set_transaction(12'h100, 32'h1, WRITE);
        finish_item(item);

        start_item(item);
        item.set_transaction(12'h100, 32'h1, READ);
        finish_item(item);
    endtask: body    
endclass: dummybus_sequence

・UVMテスト
面倒なので、仮想インタフェースと抽象クラスのテストを一緒にしています。

dummybus_test.svh
class dummybus_test extends uvm_test;
    `uvm_component_utils(dummybus_test)
    dummybus_bfm_driver bfm_drv;
    dummybus_vif_driver vif_drv;
    uvm_sequencer #(dummybus_item) bfm_seqr;
    uvm_sequencer #(dummybus_item) vif_seqr;
    dummybus_sequence seq;

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

    virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
        bfm_drv  = dummybus_bfm_driver::type_id::create("bfm_drv",this);
        vif_drv  = dummybus_vif_driver::type_id::create("vif_drv",this);
        bfm_seqr = new("bfm_seqr",this);
        vif_seqr = new("vif_seqr",this);
        seq  = dummybus_sequence::type_id::create("seq",this);
    endfunction: build_phase

    function void connect_phase(uvm_phase phase);
        bfm_drv.seq_item_port.connect(bfm_seqr.seq_item_export);
        vif_drv.seq_item_port.connect(vif_seqr.seq_item_export);
    endfunction: connect_phase

    task run_phase(uvm_phase phase);
        phase.raise_objection(this);
        seq.start(bfm_seqr);
        seq.start(vif_seqr);
        phase.drop_objection(this);
    endtask: run_phase
endclass: dummybus_test

・テストベンチ
仮想インタフェースはDUT(ram)を駆動しますが、抽象クラスはメッセージを出すだけです。

tb.sv
import uvm_pkg::*;
import dummybus_pkg::*;

module tb ();
    `include "uvm_macros.svh"
    `include "dummybus_concrete.svh"
    localparam CLOCK_PERIOD = 100;

    reg clk = 1'b0;
    dummybus_concrete bfm_adapt;

     dummybus_bfm bfm(
        .clk (clk)
     );
     dummybus_if vif(
         .clk (clk)
     );

    always begin
        #(CLOCK_PERIOD / 2);
        clk = ~clk;
    end

    ram inst_ram(
        .clk    (vif.clk),
        .w_data (vif.w_data),
        .addr   (vif.addr),
        .write  (vif.write),
        .r_data (vif.r_data)
    );

    initial begin
        bfm_adapt = new();
        uvm_config_db#(dummybus_abstract)::set(null, "*", "bfm_adapt", bfm_adapt);
        uvm_config_db#(virtual dummybus_if)::set(null, "*", "vif", vif);
        run_test();
    end

endmodule

・Modelsimのコマンドスクリプト
コマンドプロンプトやシェル等で、「vsim -c -do "run.do"」で実行できます。UVM_SRCのパスは、UVMライブラリのトップを指定してください。僕の場合、Quartus Primeについてきたライブラリを指定しています。

run.do
set UVM_SRC C:/intelFPGA_lite/18.1/modelsim_ase/verilog_src/uvm-1.2/src

vlib work
vlog -sv $UVM_SRC/uvm.sv +incdir+$UVM_SRC $UVM_SRC/dpi/uvm_dpi.cc -ccflags -DQUESTA
vlog src/ram.v
vlog src/dummybus_bfm.v 
vlog -sv src/dummybus_if.sv 
vlog -sv src/dummybus_pkg.sv +incdir+$UVM_SRC
vlog -sv src/tb.sv +incdir+$UVM_SRC
vsim -novopt +UVM_TESTNAME=dummybus_test -L work tb -do "run -all"
exit

以上です。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?