FPGA開発日記

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

FPGA開発日記 カテゴリ別インデックス

RISC-VにおけるRVWMOの仕様について読み直す

続きを読む

Vivado Simulatorを使ってUVMに入門する (3. テストのシーケンスを作る)

もう何回目になるのかわからないが、そろそろUVMを覚えなければならないのでVivado Simulatorを使ってUVMに入門してみよう。

今回はVivado 2023.2を使っている。

参考にしているのは例によって以下のウェブサイトだ:

sites.google.com

今回はUVMのスティミュラス(テストパタン)を追加する、ということになりそうだ。

  • sequenceから
    • sequence_item
    • sequencerに渡す
  • sequencerが受け取って、
  • driverに渡す

  • sequence_itemについて:トランザクションの発行に必要な情報を格納する構造体を示す。

  • model/sample_seq_item.sv

class sample_seq_item extends uvm_sequence_item;
  rand byte addr, data;
  bit  write;
  `uvm_object_utils(sample_seq_item)

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

endclass // sample_seq_item
  • driversequencerをつなぐ:sample_driver.svsample_sequencer.svをわずかに変更する。上記で作成した構造体を、driverとsequencerに渡しているもんだと思う。
class sample_driver extends uvm_driver #(sample_seq_item);
class sample_sequencer extends uvm_sequencer #(sample_seq_item);

driversequencerをつなぐ。uvm_agentを修正して、connect_phase()を追加する。

  • model/sample_agent.sv
class sample_agent extends uvm_agent;
/* ... 途中省略 ... */
  function void connect_phase(uvm_phase phase);
    if(get_is_active() == UVM_ACTIVE)begin
      driver.seq_item_port.connect(sequencer.seq_item_export);
    end
  endfunction
  • interfaceの記述:とりあえず、上記のsample_seq_itemで定義した信号と同じようなものを定義する。

  • sample_if.sv

interface sample_if(input logic clk, rstz);

  logic write;  // 1:write, 0:read                                                                                                                                                                                                                                                                                                                                                        
  logic valid;
  logic [7:0] addr, data;

endinterface // sample_if                                                                                                                                                                                                                                                                                                                                                                 
  • virtual interfaceとのリンク:クラスの中に、仮想的にinterfaceを置く。この辺は良く分からない。データベースへの仮想interfaceの登録ということになる。
initial begin
  uvm_config_db#(virtual sample_if)::set(uvm_root::get(), "*.env.*", "vif", vif);
  run_test();
end

uvm_config_dbについて:指定したオブジェクトの、指定した範囲の、指定したフィールド名(変数、メンバ)に、このmoduleに組み込んだ「vif」をセットする、ということらしい(?)

  • uvm_root::get() : uvm_root::get() でuvm_testをゲットする?
  • "*.env.*" : uvm_testの中に定義しているsample_env envの中身をリンクする、という意味になる
  • "vif" : 第2引数の中で引きだしたインスタンスの各階層でvifというフィールド名を探す。
  • vif : 第3引数のフィールドに対してvifを渡す

model/sample_driver.svvirtual sample_ifを追加する。また、build_phase()も追加してuvm_config_dbを追加する。

class sample_driver extends uvm_driver #(sample_seq_item);
  virtual sample_if vif;
/* ... 以下省略 ... */
  function void build_phase(uvm_phase phase);
    bit status;
    super.build_phase(phase);
    status = uvm_config_db#(virtual sample_if)::get(this, "", "vif", vif);
    if (status==1'b0)
      uvm_report_fatal("NOVIF", {"virtual interface must be set for: ",get_full_name(),".vif"});
  endfunction // build_phase                                                                                                                                                                                                                                                                                                                                                              
  • sequenceつまり、テストベクトルを作成する。その基本的な流れは、

  • baseとなるsequenceクラスを定義して、そこに「共通記述」を埋め込む

  • sequence毎に異なる記述を、1番をextendsしたクラスの中に定義することで、記述量の削減を行う

なるほど。以下は、共通クラスとしてのsample_base_seqとそれを継承したwrite_seqの記述だ。sample_base_seqvirtualなので、そのままインスタンス化してはいけないのだと思う。

virtual class sample_base_seq extends uvm_sequence #(sample_seq_item);

class write_seq extends sample_base_seq;
  • function new() : do_no_randomizeは削除してもよい。
  • virtual task pre_body(), post_body() ここでは、raise_objection()を実行しているのと、drop_objection()を実行している。これは、実行すべきテストが存在していることを示すのだと思われる。

  • write_seqクラスでは、body()タスクを作成して、実際にシーケンスを作成している。

virtual class sample_base_seq extends uvm_sequence #(sample_seq_item);
  function new(string name="sample_base_seq");
    super.new(name);
    do_not_randomize = 1;
  endfunction // new                                                                                                                                                                                                                                                                                                                                                                      

  virtual task pre_body();
    if (starting_phase!=null) begin
       `uvm_info(get_type_name(),
                 $sformatf("%s pre_body() raising %s objection",
                           get_sequence_path(),
                           starting_phase.get_name()), UVM_MEDIUM);
       starting_phase.raise_objection(this);
    end
  endtask // pre_body                                                                                                                                                                                                                                                                                                                                                                     

  // Drop the objection in the post_body so the objection is removed when                                                                                                                                                                                                                                                                                                                 
  // the root sequence is complete.                                                                                                                                                                                                                                                                                                                                                       
  virtual task post_body();
    if (starting_phase!=null) begin
       `uvm_info(get_type_name(),
                 $sformatf("%s post_body() dropping %s objection",
                           get_sequence_path(),
                           starting_phase.get_name()), UVM_MEDIUM);
      starting_phase.drop_objection(this);
    end
  endtask
endclass

//------------------------------------------------------------------------                                                                                                                                                                                                                                                                                                                
class write_seq extends sample_base_seq;
  `uvm_object_utils(write_seq)

  function new (string name="write_seq");
    super.new(name);
  endfunction // new                                                                                                                                                                                                                                                                                                                                                                      

  virtual task body();
    $display("Hello SEQ");
    `uvm_create(req)
    req.write <= 1'b1;
    req.addr  <= 8'h10;
    req.data  <= 8'h55;
    `uvm_send(req)
    #1000;
  endtask // body                                                                                                                                                                                                                                                                                                                                                                         

endclass // write_seq                                                                                                                                                                                                                                                                                                                                                                     
  • sample_driver.svを更新し、vifを使って実際にデータを駆動し、seq_item_portから次のリクエストを受け取る記述を実装する。
  task run_phase(uvm_phase phase);
    uvm_report_info("DRIVER", "Hi");
    vif.valid <= 1'b0;
    @(posedge vif.rstz);  // wait reset negate                                                                                                                                                                                                                                                                                                                                            
    forever begin
      seq_item_port.get_next_item(req);  // wait seq_item from sequence (via sequencer)                                                                                                                                                                                                                                                                                                   
      @(posedge vif.clk); // sync clk                                                                                                                                                                                                                                                                                                                                                     
      vif.valid <= 1'b1;
      vif.write <= req.write;
      vif.addr  <= req.addr;
      vif.data  <= req.data;
      @(posedge vif.clk);
      vif.valid <= 1'b0;
      seq_item_port.item_done(rsp);
    end
  endtask

Vivado Simulatorを使ってUVMに入門する (2. sequencer, monitor, driver)

もう何回目になるのかわからないが、そろそろUVMを覚えなければならないのでVivado Simulatorを使ってUVMに入門してみよう。

今回はVivado 2023.2を使っている。

参考にしているのは例によって以下のウェブサイトだ:

sites.google.com

前回に追加して、以下のファイルを追加した:

uvm_driver

デザインに対して制御信号や入力データを与える役割を持つ。

  • sample_driver.sv
class sample_driver extends uvm_driver;
  `uvm_component_utils(sample_driver)

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

  task run_phase(uvm_phase phase);
    uvm_report_info("DRIVER", "Hi");
  endtask

endclass // sample_driver

uvm_monitor

デザインの信号を監視し、テストベンチにその情報を報告する。DUT (Device Under Test) に対して信号を駆動せず、単にDUTから出力される信号や内部の動作を観測する。

  • sample_monitor.sv
class sample_monitor extends uvm_monitor;
  `uvm_component_utils(sample_monitor)

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

  task run_phase(uvm_phase phase);
    uvm_report_info("MONITOR", "Hi");
  endtask

endclass // sample_monitor

uvm_sequencer

トランザクション(データ、命令、操作など)を生成し、それをドライバに送信する。テストデータや命令の流れを管理し、設計検証のためにDUTに入力される信号を間接的に制御する。

  • sample_sequencer.sv
class sample_sequencer extends uvm_sequencer;
  `uvm_component_utils(sample_sequencer)

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

  task run_phase(uvm_phase phase);
    uvm_report_info("SEQR", "Hi");
  endtask

endclass // sample_sequencer

uvm_agent

sequencer, driver, monitor を1つにまとめたコンポーネントで、特定の役割を持ったコンポーネントの集合体として動作する。

  • sample_agent.sv
class sample_agent extends uvm_agent;
  `uvm_component_utils(sample_agent)
  sample_driver    driver;
  sample_monitor   monitor;
  sample_sequencer sequencer;
  function new (string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    driver    = sample_driver::type_id::create("driver", this);
    monitor   = sample_monitor::type_id::create("monitor", this);
    sequencer = sample_sequencer::type_id::create("sequencer", this);
  endfunction
  task run_phase(uvm_phase phase);
    uvm_report_info("AGENT", "Hi");
  endtask
endclass

sample_env をアップデートする。

sample_agent を埋め込む。

`include "uvm_macros.svh"
import uvm_pkg::*;

class sample_env extends uvm_env;
  `uvm_component_utils(sample_env)

  sample_agent agent;

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

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

  task run_phase(uvm_phase phase);
    uvm_report_info("ENV", "Hello ENV");
  endtask
endclass

実行結果は以下のようになった。

UVM_INFO @ 0: uvm_test_top [TEST] Hello World
UVM_INFO @ 0: uvm_test_top.env [ENV] Hello ENV
UVM_INFO @ 0: uvm_test_top.env.agent [AGENT] Hi
UVM_INFO @ 0: uvm_test_top.env.agent.sequencer [SEQR] Hi
UVM_INFO @ 0: uvm_test_top.env.agent.monitor [MONITOR] Hi
UVM_INFO @ 0: uvm_test_top.env.agent.driver [DRIVER] Hi

Vivado Simulatorを使ってUVMに入門する

もう何回目になるのかわからないが、そろそろUVMを覚えなければならないのでVivado Simulatorを使ってUVMに入門してみよう。

今回はVivado 2023.2を使っている。

参考にしているのは例によって以下のウェブサイトだ:

sites.google.com

  • tb_top.sv
`timescale 1ps/1ps

module tb_top;

// UVM class library
`include "uvm_macros.svh"
import uvm_pkg::*;

// uvm user code
`include "sample_model.svh"
`include "sample_test.sv"

/////////////////////////////////////
initial begin
  run_test();
end

endmodule
  • sample_test.sv
`timescale 1ps/1ps

`include "uvm_macros.svh"
import uvm_pkg::*;

class sample_test extends uvm_test;

  `uvm_component_utils(sample_test)
  sample_env env;

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

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = sample_env::type_id::create("env", this);
  endfunction // build_phase

  task run_phase(uvm_phase phase);
    uvm_report_info("TEST", "Hello World");
  endtask // run_phase

endclass // sample_test
  • model/sample_model.svh
`include "sample_env.sv"

model/sample_env.sv

`include "uvm_macros.svh"
import uvm_pkg::*;

class sample_env extends uvm_env;
  `uvm_component_utils(sample_env)

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

  task run_phase(uvm_phase phase);
    uvm_report_info("ENV", "Hello ENV");
  endtask
endclass
  • Makefie
.PHONY: all
all: build run

SRCS += tb_top.sv
TOP_MODULE += tb_top

.PHONY: build
build:
        xvlog -sv $(SRCS) -L uvm --include ./model
        xelab $(TOP_MODULE) -L uvm -timescale 1ns/1ps

.PHONY: run
run:
        xsim $(TOP_MODULE) -R --testplusarg "UVM_TESTNAME=sample_test"
        
.PHONY: clean
clean:
        rm -rf *.log *.jou *.pb xsim.dir

実行結果は以下のようになった:

UVM_INFO /tools/Xilinx/Vivado/2023.2/data/system_verilog/uvm_1.2/xlnx_uvm_package.sv(18648) @ 0: reporter [NO_DPI_TSTNAME] UVM_NO_DPI defined--getting UVM_TESTNAME directly, without DPI
UVM_INFO @ 0: reporter [RNTST] Running test sample_test...
UVM_INFO /tools/Xilinx/Vivado/2023.2/data/system_verilog/uvm_1.2/xlnx_uvm_package.sv(20867) @ 0: reporter [UVM/COMP/NAMECHECK] This implementation of the component name checks requires DPI to be enabled
UVM_INFO @ 0: uvm_test_top [TEST] Hello World
UVM_INFO @ 0: uvm_test_top.env [ENV] Hello ENV
UVM_INFO /tools/Xilinx/Vivado/2023.2/data/system_verilog/uvm_1.2/xlnx_uvm_package.sv(13673) @ 0: reporter [UVM/REPORT/SERVER] 

Verilator 5.0のtiming controlを試す

いまさらながら、Verilator 5のtest benchのタイミング制御機能のサポートを試したいと思い、テストを開始している。

$ verilator --version
Verilator 5.020 2024-01-01 rev v5.020
  • verilator5_test.sv
module tb;

initial begin
  $display ("%t test start", $time);

  #10;

  $display ("%t test stop", $time);

  $finish;

end

endmodule // tb
$ verilator --binary -top tb verilator5_test.sv
$ ./obj_dir/Vtb                                
                   0 test start
                  10 test stop
- verilator5_test.sv:10: Verilog $finish

一応うまくいく。これでいろんな環境を構築していこうかな。

Interface 2024年11月号 「ゼロから作るマルチコアOS」を買った

特集の内容に興味があったのと、付録が気になったので買ってみた。

マルチコアOSを数千行で作れるのはとても面白そうだ。時間を取って触ってみたい。

もう一つの付録は、コンピュータ・アーキテクチャというよりも情報処理技術者試験の基本問題みたいな内容だった。 確かに、これから高校生は情報の科目を勉強しないといけないのだし、こういう特集も必要なのだな。

オープンソース・アウトオブオーダCPU NaxRiscvを概観する (Return Address Stackのソースコードを概観する1)

NaxRiscvのRAS(Return Address Stack)に関連するソースコードを読んでいきたいと思う。

基本的にはSpinal-HDLのブロックを引っ張り出してVerilogのモジュールでまとめ上げて、わかりにくい展開されたような論理をまとめていったうえで中身を解析する。

まずはRASのメモリの部分から。RASのスタックメモリは以下のように定義されている。

(* ram_style = "distributed" *) reg [39:0] DecoderPredictionPlugin_logic_ras_mem_stack [0:15];

書き込みと読み込みの論理は単純だが、投機的な動作が不明だ:

assign DecoderPredictionPlugin_logic_ras_write_valid           = FrontendPlugin_serialized_isFireing &
                                                                 (FrontendPlugin_serialized_Frontend_DISPATCH_MASK_0 && FrontendPlugin_serialized_RAS_PUSH_0 |
                                                                  FrontendPlugin_serialized_Frontend_DISPATCH_MASK_1 && FrontendPlugin_serialized_RAS_PUSH_1);
assign DecoderPredictionPlugin_logic_ras_write_payload_address = DecoderPredictionPlugin_logic_ras_ptr_push;
assign DecoderPredictionPlugin_logic_ras_write_payload_data    = FrontendPlugin_serialized_Frontend_DISPATCH_MASK_0 && FrontendPlugin_serialized_RAS_PUSH_0 ? FrontendPlugin_serialized_PC_INC_0 :
                                                                 FrontendPlugin_serialized_Frontend_DISPATCH_MASK_1 && FrontendPlugin_serialized_RAS_PUSH_1 ? FrontendPlugin_serialized_PC_INC_1 :
                                                                 'h0;
always @(posedge clk) begin
  if(DecoderPredictionPlugin_logic_ras_write_valid) begin
    DecoderPredictionPlugin_logic_ras_mem_stack[DecoderPredictionPlugin_logic_ras_write_payload_address] <= DecoderPredictionPlugin_logic_ras_write_payload_data;
  end
end

PushとPopの論理はほとんど一緒なのだが、これでいいのかなあ?

      val ptr = new Area{
        val push = Reg(UInt(log2Up(rasDepth) bits)) init(0)
        val pop = Reg(UInt(log2Up(rasDepth) bits)) init(rasDepth-1)
        val pushIt, popIt = False

        push := push + U(pushIt) - U(popIt)
        pop  := pop + U(pushIt) - U(popIt)
      }

あとは、ROBからリスケジュールの信号がくると、それに基づいてPushとPopのポインタを戻す。

    if(CommitPlugin_logic_reschedule_reschedulePort_valid) begin
      DecoderPredictionPlugin_logic_ras_ptr_push <= DecoderPredictionPlugin_logic_ras_healPush;
      DecoderPredictionPlugin_logic_ras_ptr_pop  <= DecoderPredictionPlugin_logic_ras_healPop;
    end else begin