FPGA開発日記

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

Vivado Simulatorを用いてUVMに入門する (2. テストベンチの解析)

UVMに入門したくて、簡単な例を用いて試してみることにした。以下のようなシンプルなデザインをテストしたい。

vlsiverify.com

adderとのインタフェースとして、以下を定義する。これによりテストベンチとDUTを接続する。

  • verify/addr_if.sv
interface add_if(input logic clk, reset);

logic [ 7: 0] ip1, ip2;
logic [ 8: 0] out;

endinterface // add_if

次に、SequencerとDriverについて見て行く。Sequencerはテストベンチを作成するところだ。 まず、seq_itemによって、ドライブしたい要素をリストアップしていくらしい。ip1ip2はランダムに駆動するものと思われるが、outは値を受け取るだけなので駆動しない。

  • verify/seq_item.sv
 `include "uvm_macros.svh"
import uvm_pkg::*;

class seq_item extends uvm_sequence_item;
  rand bit [ 7: 0] ip1, ip2;
  bit [ 8: 0]      out;

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

  `uvm_object_utils_begin(seq_item)
    `uvm_field_int(ip1, UVM_ALL_ON)
    `uvm_field_int(ip2, UVM_ALL_ON)
  `uvm_object_utils_end

  constraint ip_c {ip1 < 100; ip2 < 100;}

endclass // seq_item

これに基づいて、seqcrを定義する。seq_itemを拡張する形で定義する。これはテンプレートとして使えそうな構成だ。

  • verify/seqcr.sv
 `include "uvm_macros.svh"
import uvm_pkg::*;

class seqcr extends uvm_sequencer#(seq_item);
  `uvm_component_utils(seqcr)

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

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

endclass // seqcr

これに基づいてDriverを定義する。Driverはadder_ifを駆動するものだ。ポイントはvif.ip1vif.ip2を駆動するところだ。 どうもこれはseq_item_port.get_next_item(req)seq_item_port.item_done()で囲むところがポイントらしい。

  • verify/driver.sv
 `include "uvm_macros.svh"
import uvm_pkg::*;

class driver extends uvm_driver#(seq_item);
  virtual add_if vif;
  `uvm_component_utils(driver)

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

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if(!uvm_config_db#(virtual add_if) :: get(this, "", "vif", vif))
      `uvm_fatal(get_type_name(), "Not set at top level");
  endfunction

  task run_phase (uvm_phase phase);
    forever begin
      // Driver to the DUT
      seq_item_port.get_next_item(req);
      `uvm_info(get_type_name, $sformatf("ip1 = %0d, ip2 = %0d", req.ip1, req.ip2), UVM_LOW);
      vif.ip1 <= req.ip1;
      vif.ip2 <= req.ip2;
      seq_item_port.item_done();
    end
  endtask // run_phase

endclass // driver

Vivado Simulatorを用いてUVMに入門する (1. adderを用いたシンプルな例)

UVMに入門したくて、簡単な例を用いて試してみることにした。以下のようなシンプルなデザインをテストしたい。

vlsiverify.com

module adder
  (input logic       clk,
   input logic          reset,
   input logic [ 7: 0]  in1,
   input logic [ 7: 0]  in2,
   output logic [ 8: 0] out
   );

always@(posedge clk, posedge reset) begin
  if (reset)
    out <= 'h0;
  else
    out <= in1 + in2;
end

endmodule // adder

つまり、入力in1in2に対して、出力outが正しく出力されていることを確認したいというわけだ。1サイクル後に結果が出力される。

上記のリファレンスによると、UVMで以下のような構成を想定するらしい。

必要な環境は:

  • TB_top : テストベンチ全体を含む。テスト環境とインタフェース・DUTをすべて含む
  • Test :テスト環境を含む。エージェントとスコアボードを含む
  • Agent:テストを駆動する側。シーケンサ・ドライバ・モニタを含む
  • Sequencer : おそらくはテストシーケンスを生成する側。
  • Driver:シーケンスを元にインタフェースをドライブする。
  • Monitor:インタフェースを通じて結果を取得する。
  • Scoreboard:結果を想定する値と比較する。
.
├── design
│   └── adder.sv
├── verify
│   ├── add_if.sv
│   ├── agent.sv
│   ├── base_seq.sv
│   ├── base_test.sv
│   ├── driver.sv
│   ├── env.sv
│   ├── monitor.sv
│   ├── scoreboard.sv
│   ├── seq_item.sv
│   ├── seqcr.sv
│   └── tb_top.sv
└── xsim
    ├── Makefile
    └── filelist.vf

まずは、Vivado Simulatorを用いてUVMのシミュレーションを行う。

.PHONY: all
all: build run

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

.PHONY: run
run:
        xsim $(TOP_MODULE) -R --testplusarg "UVM_TESTNAME=base_test" -testplusarg "UVM_VERBOSITY=UVM_LOW"
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 base_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 /home/kimura/work/sv/uvm_adder/verify/base_seq.sv(10) @ 5000: uvm_test_top.env_o.agt.seqr@@bseq [base_seq] Base seq: Inside Body
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/driver.sv(22) @ 5000: uvm_test_top.env_o.agt.drv [driver] ip1 = 34, ip2 = 44
----------------------------------------------------------------------------------------------------------
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/scoreboard.sv(29) @ 10000: uvm_test_top.env_o.sb [scoreboard] Matched: ip1 = 34, ip2 = 44, out = 78
----------------------------------------------------------------------------------------------------------
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/base_seq.sv(10) @ 10000: uvm_test_top.env_o.agt.seqr@@bseq [base_seq] Base seq: Inside Body
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/driver.sv(22) @ 10000: uvm_test_top.env_o.agt.drv [driver] ip1 = 31, ip2 = 31
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/base_seq.sv(10) @ 15000: uvm_test_top.env_o.agt.seqr@@bseq [base_seq] Base seq: Inside Body
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/driver.sv(22) @ 15000: uvm_test_top.env_o.agt.drv [driver] ip1 = 50, ip2 = 59
----------------------------------------------------------------------------------------------------------
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/scoreboard.sv(29) @ 18000: uvm_test_top.env_o.sb [scoreboard] Matched: ip1 = 31, ip2 = 31, out = 62
----------------------------------------------------------------------------------------------------------
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/base_seq.sv(10) @ 20000: uvm_test_top.env_o.agt.seqr@@bseq [base_seq] Base seq: Inside Body
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/driver.sv(22) @ 20000: uvm_test_top.env_o.agt.drv [driver] ip1 = 9, ip2 = 14
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/base_seq.sv(10) @ 25000: uvm_test_top.env_o.agt.seqr@@bseq [base_seq] Base seq: Inside Body
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/driver.sv(22) @ 25000: uvm_test_top.env_o.agt.drv [driver] ip1 = 44, ip2 = 87
----------------------------------------------------------------------------------------------------------
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/scoreboard.sv(29) @ 26000: uvm_test_top.env_o.sb [scoreboard] Matched: ip1 = 9, ip2 = 14, out = 23
----------------------------------------------------------------------------------------------------------
...
----------------------------------------------------------------------------------------------------------
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/scoreboard.sv(29) @ 50000: uvm_test_top.env_o.sb [scoreboard] Matched: ip1 = 9, ip2 = 74, out = 83
----------------------------------------------------------------------------------------------------------
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/base_seq.sv(10) @ 50000: uvm_test_top.env_o.agt.seqr@@bseq [base_seq] Base seq: Inside Body
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/driver.sv(22) @ 50000: uvm_test_top.env_o.agt.drv [driver] ip1 = 53, ip2 = 9
UVM_INFO /home/kimura/work/sv/uvm_adder/verify/base_test.sv(24) @ 50000: uvm_test_top [base_test] End of testcase
UVM_INFO /tools/Xilinx/Vivado/2023.2/data/system_verilog/uvm_1.2/xlnx_uvm_package.sv(19968) @ 50000: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /tools/Xilinx/Vivado/2023.2/data/system_verilog/uvm_1.2/xlnx_uvm_package.sv(13673) @ 50000: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---

** Report counts by severity
UVM_INFO :   32
UVM_WARNING :    0
UVM_ERROR :    0
UVM_FATAL :    0
** Report counts by id
[NO_DPI_TSTNAME]     1
[RNTST]     1
[TEST_DONE]     1
[UVM/COMP/NAMECHECK]     1
[UVM/RELNOTES]     1
[base_seq]    10
[base_test]     1
[driver]    10
[scoreboard]     6

$finish called at time : 50 ns : File "/tools/Xilinx/Vivado/2023.2/data/system_verilog/uvm_1.2/xlnx_uvm_package.sv" Line 18699
exit
INFO: [Common 17-206] Exiting xsim at Thu Mar 14 09:01:44 2024...

これ、良く分からんけど6個しかテスト動いて無くない?でも、base_seqdriverが10回動いているから大丈夫なのかしら。 これから、このデザインを読み解いていこうと思う。

RISC-Vの高性能アウト・オブ・オーダCPU XiangShanについての資料を読む (1. 概要)

HPCA2024で開催された、RISC-Vのアウト・オブ・オーダCPU XiangShanについての資料が公開されていたので、読んでみることにした。 じっくり読みたいので、1ページずつ要点を抑えていく。

xiangshan-doc.readthedocs.io


現在、オープンスタンダードとして知られるRISC-Vは、企業がどのようにチップを作るかという手法を変える可能性がある。

オープンソースソフトウェアエコシステムの成功を模倣し、ハードウェアもオープン化できないか?

チップ設計のバリアを低減するために、IPやEDAツールのコストを抑えることが肝要。

オープン・ソース・チップの3つの段階:

  • ISAがオープン
  • 設計、実装がオープン
  • フレームワーク・ツール(この場合はEDAツールなど)がオープン

それぞれが、このように対応する。

オープン・ソース・ハイパフォーマンスRISC-Vプロセッサがなぜ必要か?

  • RISC-VはフリーでオープンなISA
  • 多くのRISC-VコアはIoT/Edge向けだが、アカデミックや産業計は高性能RISC-Vプロセッサを望んでいる。
  • オープンでイノベーティブなハードウェアプラットフォームを提供することによって、ハードウェア界隈を変えていくという目的がある。
    • 「Linuxのハードウェア・バージョン」

XiangShanは北京にある香山に由来する。

XiangShanは3つのバージョンが存在する。

  • YQH
    • RV64GC スーパスカラOoO シングルコア
    • 28nm テープアウト済み
    • SPEC CPU 2006 7.01@1GHz
  • NH
    • RV64GCBK スーパスカラ OoO デュアルコア
    • 14nm テープアウト予定、2GHz動作
    • SPECint 2006 19.01@2GHz
  • KMH
    • RV64GCBKHV スーパスカラ OoO クアッドコア
    • 3GHz動作、NHの1.5倍のIPC
    • インダストリアルと綿密な連携

第1世代のXiangShan (Yanqihu) はRV64GCの11ステージ・アウトオブオーダ・スーパスカラ

  • 5.3Coremark / MHz
  • SPEC CPU 2006 7@1GHz with DDR4-1600

XiangShan V1は2021年にテープアウトされ、2022に実チップ動作確認。

XiangShan V2 (Nanhu) は以下の変更を加えている。

  • フロントエンドの向上:命令フェッチと分岐予測ユニットを分離。
  • バックエンドの向上:スケジューラの向上、命令フュージョン、Move Eliminationなど
  • L2/L3キャッシュ:ハイブリッド・プリフェッチャの導入
  • デュアルコアへの構成変更:RV64GCBK

XiangShan V2は、SPECにおいて更なる性能向上を見込んでいる。FPGAでの検証をベースにした結果。

FPGAでのプロトタイプ実証の結果。

XiangShan V3 (Kunminghu) はARM Neoverse N2をターゲットとし、3GHzでSPECCPU 2006 45を目指す。

さらに、ハイパーバイザ・ベクトル命令のサポートを目指す。

XingaShan V3の変更点:

  • 機能向上
    • ハイパーバイザ・ベクトル命令のサポ―tお
    • CHIプロトコルのサポート
  • 性能向上
    • フロントエンド・バックエンド・ロードストアユニットおよびキャッシュの性能向上
    • RTLより計測されたパフォーマンスモデルの作成
    • パフォーマンスモデルによる性能検証からの、RTLのファインチューニング
  • 機能検証
    • 階層検証フローおよびFPGAによるプロトタイプ
    • 産業レベルの検証環境の導入
  • 物理設計
    • 物理設計チームの導入
    • RTLコーディングの同じイタレーションで、タイミング評価を行う。

XiangShan V3は、SPECにおいて更なる性能向上を予測している。

XiangShan V3は、性能においてNeoverse V2の相当する性能を予定している。

XiangShanのBig CoreとMid Coreを用意することによって、デュアルコア構成環境を想定している。

SystemVerilogにおけるInterface内の関数を使うときのセンシティビティ・リストの考慮事項

SystemVerilogで回路記述をしていて、引っかかったところのメモ:

www.edaplayground.com

SystemVerilogのInterface内でFunctionを定義した場合、これを使う場合には注意が必要だ。

interface interface_counter;
logic valid;
logic [ 3: 0] counter;

function automatic logic is_full();
  return valid & (&counter);
endfunction // is_full

modport master (output valid, counter, import is_full);
modport slave  (input  valid,          import is_full);

endinterface // interface_counter

上記のlogic_is_full()を、インタフェースを通じてassignで接続した場合、Verilogの記述的に正しく信号が伝搬できないようだ(論理合成して正しい回路が出てくるかはわからない)。

// これではcounter_is_fullの値は正しく更新されない
assign counter_is_full = counter_if.is_full();

これは、functionが動作するセンシティビティ・リストが正しくリストアップできないのが問題で、インタフェース内の信号が切り替わったとしても、それに応じて関数の出力がアップデートされるとは限らないからだ。 この場合は、always_combを使用する必要がある。

always_comb begin
  counter_is_full = counter_if.is_full();
end

自作CPUの回路面積削減検討 (ROBの面積削減検討)

自作CPUの回路面積削減の続き。次はROBの面積削減を検討する。

ROBもCAMで構成する要素と、RAMで構成する要素がある。

これまですべてCAMで構成する要素を、RAMに変更して構成する。

// Instruction's static information from decoder                                                                                                                                                                                                                                                                                                                                          
typedef struct packed {
  vaddr_t            pc_addr;
  reg_wr_disp_t      wr_reg;
  logic [31: 0]      inst;
`ifdef SIMULATION
  logic              rvc_inst_valid;
  logic [15: 0]      rvc_inst;
  logic [63: 0]      kanata_id;
`endif // SIMULATION                                                                                                                                                                                                                                                                                                                                                                      
} rob_entry_payload_t;

typedef struct packed {
  rob_entry_payload_t [DISP_SIZE-1: 0] disp;
} rob_payload_t;
distributed_ram
  #(.WIDTH($bits(rob_payload_t)),
    .WORDS(CMT_ENTRY_SIZE)
    )
u_payload_ram
  (
   .i_clk     (i_clk    ),
   .i_reset_n (i_reset_n),

   .i_wr     (rn_front_if.valid & rn_front_if.ready),
   .i_wr_addr(w_in_cmt_entry_id),
   .i_wr_data(w_payload_in),

   .i_rd_addr(w_out_cmt_entry_id),
   .o_rd_data(w_out_payload)
   );

これで、ROBもある程度面積が削減できた。さらに面積削減を検討していこうと思う。

自作CPUの回路面積削減検討 (ストアバッファの面積削減検討)

自作CPUの回路面積削減の続き。ストアバッファの面積が結構大きいので、削減を検討する。 ストアバッファが大きくなっている原因は、直接的なものはよくわからないのだが、Atomic命令を処理するフィールドが少なくともかなり面積を使用しているので、これを削減することを考える。

Atomic命令を処理するエントリはエントリの1番目のみに限定し、それ以外のエントリはAtomicをサポートしない。

generate for (genvar e_idx = 0; e_idx < ST_BUF_ENTRY_SIZE; e_idx++) begin : entry_loop
  logic w_ready_to_merge;
  logic w_load;

  assign w_entry_valids[e_idx] = w_entries[e_idx].valid;

  assign w_load = st_buffer_if.valid & (st_buffer_if.is_rmw ? e_idx == 0 : w_in_ptr_oh[e_idx]) & w_st_buffer_allocated;

  if (e_idx == 0) begin
    scariv_st_buffer_amo_entry
      u_entry
/* ... 途中省略 ... */
         );
  end else begin // if (e_idx == 0)
    scariv_st_buffer_entry
      u_entry
        (
         .i_clk    (i_clk    ),
         .i_reset_n(i_reset_n),
/* ... 途中省略 ... */

どっちにしろ、Atomic命令を処理するときはストアバッファが空になっていることを確認してからキャッシュアップデートをするので、一応問題ないはずだ。

一応面積的には大幅な削減になっている。やはりAtomic命令の処理のところでかなり面積を消費していたんだなあ。

  • 削減処理前

  • 削減処理後

自作CPUの回路面積削減検討 (命令発行条件の緩和検討)

自作CPUのいくつかの面積最適化を行っていたら、またバグが出てきた。今度はデッドロック系だ。 問題は、LSUの命令発行キューに対してリプレイキューが小さく設計してあることにある。 例えば、リプレイキューのエントリ数がNであり、リプレイキューまでのパイプライン段数がAだとすると、リプレイキューがN-Aエントリまで埋まったとすると、それ以上命令を発行することはできない。 この時にデッドロックの可能性が浮上する。より古い命令が実行しようにも、命令キューからの発行を止められてしまう可能性があるということだ。

しかし、この問題は冷静に考えてみると、例えばコア内で最も古い命令はリプレイキューに入る可能性は低く、またどっちにしろ最も古い命令を1命令だけ発行したところでリプレイキューはN-(N-A)=Aエントリ余っているのだから、実行してしまって問題ない。

この観点から、命令例発行キューの発行条件を若干緩和し、コア内で最も古い命令は待ち合わせをすることなく優先的かつリプレイキューの条件を見ないで発行できるように変更した。 これにより、AAPGなどの非常に複雑なランダムテストでも全くデッドロックすることなく流れるようになった。