LoginSignup
1
0

More than 5 years have passed since last update.

[SystemVerilog]interfaceを入れてuvm_driverに似た何かを作る。

Posted at

はじめに

前回の終わりに、モデルをUVMドライバ化と書きましたが、interfaceも含めて一気に書くと、 自分がたぶん忘れるわかりにくくなると思いました。
ここではワンクッション置いて、interfaceを入れ、さらにuvmクラスを入れようと思います。

コード

共通コード

前回と同じです。

interface

全部の信号をinterfaceにまとめ、検証対象(DUT)のメソッドを入れてみました。

apbif.sv
interface apb_t();

  logic   rst_n, clk;
  logic   sel;
  logic   enable;
  logic   write;
  addr_t  addr;
  data_t  wdata;
  data_t  rdata;

  function logic get_sel();
    return sel;
  endfunction

  function logic get_en();
    return enable;
  endfunction

  function logic get_rw();
    return write;
  endfunction

  function data_t get_data();
    return wdata;
  endfunction

  function data_t put_data(data_t data_i);
    rdata  = data_i;
    return rdata;
  endfunction

  modport master (
    input  rst_n, clk,
    output enable, sel, write, addr, wdata,
    input  rdata
    );

  modport slave (
    import  get_en, get_rw, get_sel, get_data, put_data,
    input   rst_n, clk,
    input   enable, sel, write, addr, wdata,
    output  rdata
    );

endinterface

DUT

interfaceに応じて、信号名を変えています。

apb_slave.sv
module apb_slave #(
  parameter OFFSET = 32'h0000_0000
  )(
  apb_t.slave    apb_slv
  );

  localparam MW = 256;

  addr_t  addr_d;
  data_t  [(MW -1) : 0 ] mem ;

  always_ff @(negedge apb_slv.rst_n, posedge apb_slv.clk)
    if(! apb_slv.rst_n)
      addr_d <= '0;
    else
      addr_d <= apb_slv.addr;

  always_ff @(negedge apb_slv.rst_n, posedge apb_slv.clk)
    if(! apb_slv.rst_n)
      mem <= '0;
    else
      if(apb_slv.get_en() && apb_slv.get_sel() && apb_slv.get_rw() && (addr_d>=OFFSET) && (addr_d< OFFSET + MW) )
        mem[addr_d - OFFSET ] <= apb_slv.get_data();

  always_comb begin
    if(apb_slv.get_en() && apb_slv.get_sel() && (! apb_slv.get_rw()) && (addr_d>=OFFSET) && (addr_d < OFFSET + MW) )
      apb_slv.put_data( mem [addr_d - OFFSET] );
    else
      apb_slv.rdata = '0;
  end

endmodule

モデル

こちらもinterfaceに合わせます。

apb_master_model.sv
module abp_master(
  apb_t.master   apb_mst
  );

  task init();
    apb_mst.addr   = '0;
    apb_mst.sel    = '0;
    apb_mst.enable = '0;
    apb_mst.write  = '0;
    apb_mst.wdata  = 'z;
  endtask

  task do_write(int addr_i, data_t wdata_i);
    @(posedge apb_mst.clk);

    apb_mst.addr   <= #1 addr_i;
    apb_mst.sel    <= #1 1'b1;
    apb_mst.enable <= #1 1'b0;
    apb_mst.write  <= #1 1'b1;
    apb_mst.wdata  <= #1 wdata_i;

    @(posedge apb_mst.clk)
    apb_mst.enable <= #1 1'b1;

    @(posedge apb_mst.clk)

    apb_mst.addr   <= #1 '0;
    apb_mst.sel    <= #1 '0;
    apb_mst.enable <= #1 '0;
    apb_mst.write  <= #1 '0;
    apb_mst.wdata  <= #1 'z;
  endtask

  task do_read(int addr_i, output data_t rdata_o);

    @(posedge apb_mst.clk)
    apb_mst.addr   <= #1 addr_i;
    apb_mst.sel    <= #1 1'b1;
    apb_mst.enable <= #1 1'b0;
    apb_mst.write  <= #1 1'b0;

    @(posedge apb_mst.clk)
    apb_mst.enable <= #1 1'b1;

    @(posedge apb_mst.clk)
    rdata_o = apb_mst.rdata;

    apb_mst.addr   <= #1 '0;
    apb_mst.sel    <= #1 '0;
    apb_mst.enable <= #1 '0;
    apb_mst.write  <= #1 '0;
  endtask

  initial
    init();

endmodule

テスト

intterfaceを入れたので、モジュール内で宣言する信号が無くなりました。後は変わりません。UVMのクラス部分は後述します。

tb.sv
module tb();

  apb_t apb_bus();

  apb_slave  #(.OFFSET(32'h0001_0000)) dut (.apb_slv(apb_bus), .*);

  abp_master tu(.apb_mst(apb_bus), .*);

  task clk_gen();
    apb_bus.clk = 0;
    forever
      #5 apb_bus.clk = ~ apb_bus.clk;
  endtask

  class base_drv extends uvm_driver;
    `uvm_component_utils(base_drv)

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

    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
    endfunction

    virtual task reset_phase(uvm_phase phase);

      phase.raise_objection(this);

      `uvm_info("base_drv", "Reset Start", UVM_MEDIUM)

      apb_bus.rst_n = '0;
      repeat(5) @(posedge apb_bus.clk);
      apb_bus.rst_n = '1;
      repeat(5) @(posedge apb_bus.clk);

      `uvm_info("base_drv", "Reset End", UVM_MEDIUM)

      phase.drop_objection(this);

    endtask

    virtual task main_phase(uvm_phase phase);
      phase.raise_objection(this);
      `uvm_info("test", "Start", UVM_MEDIUM);
      `uvm_fatal(get_type_name(), "No test");
      phase.drop_objection(this);
    endtask

  endclass

  class test_drv extends base_drv;
    `uvm_component_utils(test_drv)

    int  base_addr = 'h1_0000;

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

    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
    endfunction

    virtual task main_phase(uvm_phase phase);
      data_t  wdata;
      data_t  mem_t [int];

      phase.raise_objection(this);

      `uvm_info("test_drv", "Start", UVM_MEDIUM);

      for (int i = 0; i < 'h100; i += 4) begin
        apb_bus.addr = base_addr + i;
        wdata = $urandom_range(32'hFFFF, 0);

        tu.do_write(apb_bus.addr, wdata);
        mem_t[apb_bus.addr] = wdata;

        `uvm_info("test_drv",
          $sformatf("Write addr == 0x%8h, wdata == 0x%8h", apb_bus.addr, wdata),
          UVM_MEDIUM);
      end

      for (int i = 0; i < 'h100; i += 4) begin
        apb_bus.addr = base_addr + i;
        tu.do_read(apb_bus.addr, apb_bus.rdata);

         if(apb_bus.rdata != mem_t[apb_bus.addr]) begin
            `uvm_error("Conpare Error",
               $sformatf("Read addr == 0x%8h, exdata == 0x%8h, rdata == 0x%8h", apb_bus.addr, mem_t[apb_bus.addr], apb_bus.rdata));
         end
         else begin
            `uvm_info("Compare Success",
               $sformatf("Read addr == 0x%8h, exdata == 0x%8h, rdata == 0x%8h", apb_bus.addr, mem_t[apb_bus.addr], apb_bus.rdata),
               UVM_MEDIUM);
         end

      end

      phase.drop_objection(this);
    endtask

  endclass

  class base_test extends uvm_test;
    `uvm_component_utils(base_test)

    test_drv drv;

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

    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      drv = test_drv::type_id::create("drv", this);
    endfunction

  endclass

  initial
    fork
      clk_gen();
      run_test();
    join_none

endmodule

テストについて

いろんな資料を見ると、uvm_driverというクラスがDUTとのピンレベルでのやり取りを行うようです。しかし、前回まで見てきたように、これまでDUTを叩いていたのはuvm_testクラスです。UVMのソースコードを拝見すると、uvm_testクラスとuvm_driverクラスは「uvm_component」というクラスを継承しています。

https://verificationacademy.com/sessions/uvm-connecting-components/rte/japanese-pdf
より引用。

uvm_class_layer.png

ということで、uvm_testでもピンレベルのやり取りが可能です。 この辺の緩さがなんとも微妙ですね。 よって、このコードでは、継承するクラスをuvm_testクラスからuvm_driverクラスに変更しただけです(名前を変えただけ)。

その代わり、uvm_driverに変更したからuvm_testクラスを持ってきて継承し、テストを記述しなくてはなりません。その辺が、「class base_test extends uvm_test...」辺りの記述になります。

このクラス内では、先のtest_drvクラスを宣言し、インスタンスします。
uvm_componentクラスを継承してきたクラスは、new関数でインスタンスするのではなく、createと呼ばれるメソッド(おなじない)でインスタンスします。書式はあんな感じです。

おわりに

ということで、uvm_driverクラスを使って、uvm_driverに似た何かを作ってみました。APBマスターモデルをクラス化しないと、uvm_driverとは 個人的に言えるとは思えません。

というわけで、次回はいつかAPBマスターをクラス化し、UVMドライバとしてDUTとinterfaceを介してつないでみたいと思います。

1
0
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
1
0