FPGA開発日記

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

Facetsでプロセッサベンチマークソフトウェアの解析を試行した話2

引き続き、Facetsで遊んでいる。現在はDhrystoneプログラムを解析中。

レジスタ読み込みアドレスとレジスタ書き込みアドレスの関係を調査していたのだが、いくつか読み込ませるデータを変更して、より解析しやすくしてみた。

sed 's/C0://g' output/dhrystone.riscv.out | \
  grep "\[1\] pc" | \
  sed "s/inst=\(.*\)\] \(.*\)$/\inst=\1| \
  \2/g" | sed 's/\]/|/g' | \
  sed 's/pc=\[//g' | \
  sed 's/W\[//g' | \
  sed 's/R\[//g' | \
  sed 's/inst=\[//g' | \
  sed 's/|\[/| /'g | \
  sed 's/\[/| /g' | \
  sed 's/r *\([0-9]*\)=/\1|/g' > output/dhrystone.riscv.en.csv

awk -F'|' '{OFS="|"; if($6 == 0){ $4=""; $5=""; } print $0}' dhrystone.riscv.en.csv  > dhrystone.riscv.en2.csv

詳細は省略するが、レジスタ書き込みを起こさない命令については、書き込みアドレスの情報を削り取ってしまい、無意味な情報はFacetsで可視化されないようにする。

  • dhrystore.riscv.en2.csv の中身
...
     151863 | 1| 0000000344| 8|0000000080002a48| 1| 8|0000000080002a40| 8|0000000080005113| 00840413| addi    s0, s0, 8
     151864 | 1| 0000000348||| 0| 8|0000000080002a48| 9|0000000080005110| fe941ce3| bne     s0, s1, pc - 8   <-- 書き込みを発生させないプログラムは、フィールドを空白にする。
     151865 | 1| 0000000340||| 0| 8|0000000080002a48| 0|0000000000000000| 00043023| sd      zero, 0(s0)
     151866 | 1| 0000000344| 8|0000000080002a50| 1| 8|0000000080002a48| 8|0000000080005113| 00840413| addi    s0, s0, 8
     151867 | 1| 0000000348||| 0| 8|0000000080002a50| 9|0000000080005110| fe941ce3| bne     s0, s1, pc - 8
     151868 | 1| 0000000340||| 0| 8|0000000080002a50| 0|0000000000000000| 00043023| sd      zero, 0(s0)
     151869 | 1| 0000000344| 8|0000000080002a58| 1| 8|0000000080002a50| 8|0000000080005113| 00840413| addi    s0, s0, 8

これで再度Facetsで表示させてみた。

横軸が読み込みレジスタアドレス、縦軸が書き込みレジスタアドレスなので、

  • レジスタアドレス0-3と、アドレス13-15に書き込みが発生しやすい。
  • レジスタアドレス0-3が、読み込みアドレスとしては圧倒的に使用される。

ということが分かってくるだろう。

f:id:msyksphinz:20171011002219g:plain

最内周ループの探索

PCでソートして、最も実行される命令を探索してみた。

f:id:msyksphinz:20171011002809g:plain

PC=0x1984 ~ 0x1992くらいが最頻で実行されるPCだなー。このへんが最内周ループか。分類してみると、分岐がやはり多いイメージだな。

Facetsでプロセッサベンチマークソフトウェアの解析を試行した話

Facetsというのは、Googleがリリースしたデータ解析ツールだ。機械学習に使えるとしており、データを可視化するのが得意で、ダイナミックな動きが特徴のデータ可視化ツールセットとなっている。

これを使って何かできないかと考えたのだが、例えばCPUベンチマークプログラムの結果を投入してプログラムの解析に使えないだろうか?

f:id:msyksphinz:20171009194155g:plain

Facetsを使って解析する内容

RISC-VのRocket Chipを使うと、ベンチマークプログラムのログを取得することが出来る。これをFacetsに投入して、グラフ化してもらうというわけだ。

Rocket-Chipを使うと、以下のようなトレースファイルを取得することが出来る。

C0:        386 [0] pc=[000000081c] W[r 0=0000000000000400][0] R[r 8=0000000000000000] R[r 0=0000000000000000] inst=[40044403] lbu     s0, 1024(s0)
C0:        387 [1] pc=[0000000820] W[r 8=0000000000000000][1] R[r 8=0000000000000000] R[r 1=0000000000000003] inst=[00147413] andi    s0, s0, 1
C0:        388 [1] pc=[0000000824] W[r 0=0000000000000000][0] R[r 8=0000000000000000] R[r 0=0000000000000000] inst=[02041063] bnez    s0, pc + 32
C0:        389 [0] pc=[0000000824] W[r 0=0000000000000000][0] R[r 8=0000000000000000] R[r 0=0000000000000000] inst=[02041063] bnez    s0, pc + 32
C0:        390 [0] pc=[0000000824] W[r 0=0000000000000000][0] R[r 8=0000000000000000] R[r 0=0000000000000000] inst=[02041063] bnez    s0, pc + 32
C0:        391 [0] pc=[0000000824] W[r 0=0000000000000000][0] R[r 8=0000000000000000] R[r 0=0000000000000000] inst=[02041063] bnez    s0, pc + 32
C0:        392 [1] pc=[0000000828] W[r 8=0000000000000000][1] R[r 0=0000000000000000] R[r20=0000000000000003] inst=[f1402473] csrr    s0, mhartid
C0:        393 [0] pc=[0000000828] W[r 0=0000000000000000][0] R[r 0=0000000000000000] R[r20=0000000000000003] inst=[f1402473] csrr    s0, mhartid
C0:        394 [0] pc=[0000000828] W[r 0=0000000000000000][0] R[r 0=0000000000000000] R[r20=0000000000000003] inst=[f1402473] csrr    s0, mhartid
C0:        395 [1] pc=[000000082c] W[r 0=0000000000000400][1] R[r 8=0000000000000000] R[r 0=0000000000000000] inst=[40044403] lbu     s0, 1024(s0)
C0:        396 [0] pc=[000000082c] W[r 0=0000000000000400][0] R[r 8=0000000000000000] R[r 0=0000000000000000] inst=[40044403] lbu     s0, 1024(s0)

左から順に、

これを加工して、CSVファイルにしていこう。以下sedコマンドを組んで、|で区切るcsvファイルを作成した (,を使うとニーモニック中の,と混ざるので避けた)。

sed 's/C0://g' output/dhrystone.riscv.out | grep "\[1\] pc" | sed "s/inst=\(.*\)\] \(.*\)$/\inst=\1| \2/g" | sed 's/\]/|/g' | sed 's/pc=\[//g' | sed 's/W\[//g' | sed 's/R\[//g' | sed 's/inst=\[//g' | sed 's/|\[/| /'g | sed 's/\[/| /g' | sed 's/r *\([0-9]*\)=/\1|/g' > output/dhrystone.riscv.en.csv

これで以下のようなデータセットが出来上がる。

         21 | 1| 0000010040| 10|0000000000000000| 1| 0|0000000000000000| 20|b28a1708b379bc0f| f1402573| csrr    a0, mhartid
         22 | 1| 0000010044| 11|0000000000010044| 1| 0|0000000000000000| 0|0000000000000000| 00000597| auipc   a1, 0x0
         23 | 1| 0000010048| 11|0000000000010080| 1| 11|0000000000010044| 28|b28a1708b379bc0f| 03c58593| addi    a1, a1, 60
         24 | 1| 000001004c| 0|fffffff0c1981058| 0| 0|0000000000000000| 5|b28a1708b379bc0f| 10500073| wfi (args unknown)
         29 | 1| 0000010050| 0|0000000000010052| 1| 31|000ce533b5bf589f| 29|b28a1708b379bc0f| 0000bff5| j       pc - 4
         31 | 1| 000001004c| 0|fffffff0c1981058| 0| 0|0000000000000000| 5|b28a1708b379bc0f| 10500073| wfi (args unknown)
         36 | 1| 0000010050| 0|0000000000010052| 1| 31|000ce533b5bf589f| 29|b28a1708b379bc0f| 0000bff5| j       pc - 4
         38 | 1| 000001004c| 0|fffffff0c1981058| 0| 0|0000000000000000| 5|b28a1708b379bc0f| 10500073| wfi (args unknown)
         43 | 1| 0000010050| 0|0000000000010052| 1| 31|000ce533b5bf589f| 29|b28a1708b379bc0f| 0000bff5| j       pc - 4
         45 | 1| 000001004c| 0|fffffff0c1981058| 0| 0|0000000000000000| 5|b28a1708b379bc0f| 10500073| wfi (args unknown)
         50 | 1| 0000010050| 0|0000000000010052| 1| 31|000ce533b5bf589f| 29|b28a1708b379bc0f| 0000bff5| j       pc - 4
         52 | 1| 000001004c| 0|fffffff0c1981058| 0| 0|0000000000000000| 5|b28a1708b379bc0f| 10500073| wfi (args unknown)
         57 | 1| 0000010050| 0|0000000000010052| 1| 31|000ce533b5bf589f| 29|b28a1708b379bc0f| 0000bff5| j       pc - 4
         59 | 1| 000001004c| 0|fffffff0c1981058| 0| 0|0000000000000000| 5|b28a1708b379bc0f| 10500073| wfi (args unknown)
         64 | 1| 0000010050| 0|0000000000010052| 1| 31|000ce533b5bf589f| 29|b28a1708b379bc0f| 0000bff5| j       pc - 4
         66 | 1| 000001004c| 0|fffffff0c1981058| 0| 0|0000000000000000| 5|b28a1708b379bc0f| 10500073| wfi (args unknown)
         71 | 1| 0000010050| 0|0000000000010052| 1| 31|000ce533b5bf589f| 29|b28a1708b379bc0f| 0000bff5| j       pc - 4
         73 | 1| 000001004c| 0|fffffff0c1981058| 0| 0|0000000000000000| 5|b28a1708b379bc0f| 10500073| wfi (args unknown)
         99 | 1| 0000000800| 0|0000000000000804| 1| 0|0000000000000000| 12|b28a1708b379bc0f| 00c0006f| j       pc + 0xc
        101 | 1| 000000080c| 0|0000000000000000| 0| 0|0000000000000000| 31|b28a1708b379bc0f| 0ff0000f| fence
        102 | 1| 0000000810| 0|a4f64d88d88675f3| 1| 8|43b5da18a0deb0df| 18|b28a1708b379bc0f| 7b241073| csrw    dscratch, s0
        107 | 1| 0000000814| 8|0000000000000000| 1| 0|0000000000000000| 20|b28a1708b379bc0f| f1402473| csrr    s0, mhartid
        110 | 1| 0000000818| 0|0000000000000100| 0| 0|0000000000000000| 8|0000000000000000| 10802023| sw      s0, 256(zero)
        121 | 1| 000000081c| 0|0000000000000400| 1| 8|0000000000000000| 0|0000000000000000| 40044403| lbu     s0, 1024(s0)
...

これをJupyter Notebookに取り込んで、解析してみる。以下のようにフィールドを作成して取り込んだ。

import pandas as pd
features = ["Cycle", "Enable", "PC", "Write Addr", "Write Data", "Write Enable",
            "Read Addr0", "Read Data0", "Read Addr1", "Read Data1", "Instruction", "Mnemonic"]   # フィールドを定義する。
jsonstr = pd.read_csv(
    "dhrystone.riscv.en.csv",
    names=features,
    sep=r'\s*\|\s*',    # <-- 区切り記号を|に変更する。
    engine='python',
    skiprows=[0],
    na_values="?").to_json(orient='records')

HTMLにして表示してみよう。

# Display the Dive visualization for this data
from IPython.core.display import display, HTML

HTML_TEMPLATE = """<link rel="import" href="/nbextensions/facets-dist/facets-jupyter.html">
        <facets-dive id="elem" height="600"></facets-dive>
        <script>
          var data = {jsonstr};
          document.querySelector("#elem").data = data;
        </script>"""
html = HTML_TEMPLATE.format(jsonstr=jsonstr)
display(HTML(html))

f:id:msyksphinz:20171009195324p:plain

できた!X軸とY軸をそれぞれ命令のオペランド0のRead Address、Y軸を命令の書き込みアドレスとしてみた。これにどんな意味があるのか分からないけど。

ちなみに、Read AddressもWrite Addressも「実際にそのレジスタにアクセスしているか」とは無関係に作っているのでちょっと解析結果としては微妙だ。もうすこしデータをフィルタリングしないといけないかもしれない。

本当にこの結果が正しいかどうかは置いておいて、

  • 読み込みレジスタとしては、reg0 - reg3, reg10 - reg15 が一番よくアクセスが発生する。
  • 書き込みデータとしては、reg10-reg13 が一番よくアクセスする。

ってことが分かるかなあ。もうちょっときちんと解析しないと無駄な解析だな。

VHDL/VerilogのテスティングフレームワークVUnitを試行する

VUnit は VHDL/SystemVerilogのテスティングフレームワークだ。VUnitにはいくつかテストパタンが用意されているが、少し自分でもテストパタンを用意してみよう。

作ってみたのは、32bit×32bit=64bitの符号なし整数の演算器を実装して、VUnitでテストを実行してみよう。この演算器は4サイクルで32bit×32bit=64bitの演算を実行する。

VUnitのテストスイートの実装

VUnitのテストスイートを実装してみる。基本的に、TEST_CASEにテストを定義して、テスト内容を記述している。 今回は、以下のテストを記述した。

  • test_0 :  2\times 3
  • test_1 :  2726929837 \times 13
  • test_2 :  13 \times 2726929837
  • test_3 :  475228202 \times 175707306

テストパタンを以下のように実装した。

  • vunit/examples/verilog/mult_4cycle/src/test/tb_mult_4cycle.sv
`include "vunit_defines.svh"

module tb_mult_4cycle;
   localparam integer clk_period = 20; // ns

  logic [63: 0] out_c;
  logic [31: 0] in_a, in_b;
  logic         clk   = 1'b0;
  logic         reset = 1'b1;
  logic         en    = 1'b0;

  `TEST_SUITE begin
    `TEST_SUITE_SETUP begin
      #1;
      reset = 1'b0;
    end
    `TEST_CASE("test_0") begin
      en   = 1'b1;
      in_a = 2;
      in_b = 3;

      #(clk_period * 4 * 1ns);
      en = 1'b0;
      `CHECK_EQUAL (out_c, {32'h0000_0000, in_a} * {32'h0000_0000, in_b});
    end

    `TEST_CASE("test_1") begin
      en   = 1'b1;
      in_a = 2726929837;
      in_b = 13;

      #(clk_period * 4 * 1ns);
      en = 1'b0;
      `CHECK_EQUAL (out_c, {32'h0000_0000, in_a} * {32'h0000_0000, in_b});
    end

    `TEST_CASE("test_2") begin
      en   = 1'b1;
      in_a = 13;
      in_b = 2726929837;

      #(clk_period * 4 * 1ns);
      en = 1'b0;
      `CHECK_EQUAL (out_c, {32'h0000_0000, in_a} * {32'h0000_0000, in_b});
    end

    `TEST_CASE("test_3") begin
      en   = 1'b1;
      in_a = 475228202;
      in_b = 175707306;

      #(clk_period * 4 * 1ns);
      en = 1'b0;
      `CHECK_EQUAL (out_c, {32'h0000_0000, in_a} * {32'h0000_0000, in_b});
    end
  end

  `WATCHDOG(10ms);

  always begin
    #(clk_period/2 * 1ns);
    clk <= !clk;
  end

  mult_4cycle dut
    (
     .clk   (clk),
     .reset (reset),
     .en    (en),
     .in_a  (in_a),
     .in_b  (in_b),
     .out_c (out_c)
     );

endmodule

テスト結果

f:id:msyksphinz:20171009005946p:plain

実行結果は、正しく4つのテストをPassできることを確認した。結局、Pythonフレームワークを使っているとはいえ、結局はVerilogを記述しているので、まあまあ使い易いかな。

おまけ: 4サイクル32bit符号なし整数の乗算器の実装

  • vunit/examples/verilog/mult_4cycle/src/mult_4cycle.sv
module mult_4cycle
  (
   input wire          clk,
   input wire          reset,
   input wire          en,
   input wire [31: 0]  in_a,
   input wire [31: 0]  in_b,
   output wire [63: 0] out_c
   );

  logic [63: 0]        ab_lo_lo,
                       ab_lo_hi,
                       ab_hi_lo,
                       ab_hi_hi;
  logic [31: 0]        r_a, r_b;
  always_ff @ (posedge clk, posedge reset) begin
    if (reset) begin
      r_a <= 32'h0000_0000;
      r_b <= 32'h0000_0000;
    end else begin
      if (en) begin
        r_a <= in_a;
        r_b <= in_b;
      end
    end
  end // always_ff @

  logic [ 1: 0] mul_state;
  localparam state_0 = 2'b00,
    state_1 = 2'b01,
    state_2 = 2'b10,
    state_3 = 2'b11;

  logic [63: 0] r_total;

  logic [31: 0] r_total_in;
  assign r_total_in = ((mul_state == state_0) && en) ? {16'h0000, in_a[15: 0]} * {16'h0000, in_b[15: 0]} :
                      (mul_state == state_1) ? {16'h0000, r_a[15: 0]} * {16'h0000, r_b[31:16]} :
                      (mul_state == state_2) ? {16'h0000, r_a[31:16]} * {16'h0000, r_b[15: 0]} :
                      (mul_state == state_3) ? {16'h0000, r_a[31:16]} * {16'h0000, r_b[31:16]} :
                      32'h0000_0000;

  always_ff @ (posedge clk, posedge reset) begin
    if (reset) begin
      mul_state <= state_0;
      r_total   <= 64'h0000_0000_0000_0000;
    end else begin
      case (mul_state)
        state_0 : begin
          if (en) begin
            r_total  <= {32'h0000_0000, r_total_in};
            mul_state <= state_1;
          end
        end
        state_1 : begin
          r_total   <= r_total + {16'h0000, r_total_in, 16'h0000};
          mul_state <= state_2;
        end
        state_2 : begin
          r_total   <= r_total + {16'h0000, r_total_in, 16'h0000};
          mul_state <= state_3;
        end
        state_3 : begin
          r_total   <= r_total + {r_total_in, 32'h0000_0000};
          mul_state <= state_0;
        end
      endcase // case (mul_state)
    end // else: !if(reset)
  end // always_ff @

  assign out_c = r_total;

endmodule

Design Solution Forum 2017にて発表します

そろそろ告知しておかないと自分でも忘れそう。

2017/10/13(金)に開催されるDesign Solution Forumにて、RISC-Vのトラックの一つとして発表させて頂くことになりました。

トラックB3 12:00 - 12:40 で、タイトルは "さわって分かる オープンソースプロセッサ「RISC-V」の世界" としました。 仕事としてやってきたわけでもなく、仕事から帰ってきてクタクタになりながら更新していたブログなので、情報量としてはいまいちになるかもしれませんが、興味のある皆様よろしくお願いします。

f:id:msyksphinz:20171006220753p:plain

内容としては、

  • 約3年前、RISC-Vなんて国内で誰も知らなかった時代から調査してきた「RISC-V」の成長の歴史。
    • 発表者の観点から見た、「何故注目されるようになったのか?」について
    • RISC-Vの特徴や将来について触れられたらいいな。
  • まずはアリモノから触ってみる。RISC-Vを始めたい人のためのファーストステップ
    • HiFive1でアプリケーションを書いてみた
    • Rocket ChipをFPGAに乗せて、実行 & カスタマイズしてみた

の3本立てになります(アニメみたいだ)。

とは言え、まだスライド作っている最中だし、内容変わるかもしれません。 発表下手くそですので。

という訳で、「RISC-Vは興味があるけどどうやったら良いのか分からない」「ぶっちゃけRISC-Vって将来あるの?」などなど、興味をお持ちの方は、是非いらしてください&お手柔らかにお願いします。

RISC-V 64bit Quadコア SoC "U54-MC Coreplex"

f:id:msyksphinz:20171005231219p:plain

Linuxをサポートしている64bit RISC-V 4コア + 64bit RISC-V 1コアのSoCとのこと。 Quad Coreの方はU54コアということで、アーキテクチャとしてはRV64GC(整数浮動小数点何でも入り)、シングルコアの方はE51をベースとしたRV64IMAC(整数演算のみサポート)を搭載している。

これまでに発表されたSoCは3種類あるのだが、それぞれ比較してみた。

U54‑MC Coreplex
E31 Coreplex E51 Coreplex
Core Type RV64GC U54 RV64IMAC E51 RV32IMAC E31 RV64IMAC E51
L1 Icache 32kB 4kB 16kB 16kB
L1 Dcache 32kB 8kB DTIM 64kB DTIM 64kB DTIM
Memory Protection 8 8 8 8
Local Interruption Per Core 48 48 16 16
Virtual Memory Support Sv39 - -
L2 ECC 2MB - -
DMIPS/MHz 1.7 1.61 1.61 1.8
Coremark/MHz 2.75 2.73 2.73 2.76

シングルコアの方(E51)はキャッシュのサイズが小さいな。それ以外はあまり変わらない。 Coremarkスコアの方はどれも似たり寄ったりだな。

VHDL/Verilogのテスティングフレームワーク"VUnit"

ソフトウェア環境下においてテスティングフレームワークは、例えばJUnit, GoogleTest など。 しかしハードウェア言語向けのテスティングフレームワークというのは少ない。

以前に少し紹介したのは、cocotbというテスティングフレームワーク、こちらはPythonを使用するフレームワークだ。ブログでも少し紹介した。

github.com

msyksphinz.hatenablog.com

それ以外にもう少し発見した。VUnitというVHDL/SystemVerilog用のテスティングフレームワークだ。

VUnit — VUnit documentation

f:id:msyksphinz:20171005010245p:plain

ここでは、Verilogをベースにして調査してみよう。まずはインストールから。

cd vunit
sudo python ./setup.py install

これでVUnitのバイナリがインストールされる。

次にサンプルを眺めてみよう。サンプルコードは、 examples/verilog/uart/に置いてある。

cd vunit/example/verilog/uart
python ./run.py
Compiling ../../../../../../../usr/local/lib/python2.7/dist-packages/vunit_hdl-2.2.1rc0-py2.7.egg/vunit/verilog/vunit_pkg.sv into vunit_lib ...
Compiling src/uart_tx.sv into uart_lib ...
Compiling src/uart_rx.sv into uart_lib ...
Compiling src/test/tb_uart_tx.sv into tb_uart_lib ...
Compiling src/test/tb_uart_rx.sv into tb_uart_lib ...
Starting tb_uart_lib.tb_uart_rx.test_tvalid_low_at_start
pass (P=1 S=0 F=0 T=5) tb_uart_lib.tb_uart_rx.test_tvalid_low_at_start (1.6 seconds)

Starting tb_uart_lib.tb_uart_rx.test_receives_one_byte
pass (P=2 S=0 F=0 T=5) tb_uart_lib.tb_uart_rx.test_receives_one_byte (0.1 seconds)

Starting tb_uart_lib.tb_uart_rx.test_two_bytes_cause_overflow
pass (P=3 S=0 F=0 T=5) tb_uart_lib.tb_uart_rx.test_two_bytes_cause_overflow (0.2 seconds)

Starting tb_uart_lib.tb_uart_tx.test_send_one_byte
pass (P=4 S=0 F=0 T=5) tb_uart_lib.tb_uart_tx.test_send_one_byte (0.2 seconds)

Starting tb_uart_lib.tb_uart_tx.test_send_many_bytes
pass (P=5 S=0 F=0 T=5) tb_uart_lib.tb_uart_tx.test_send_many_bytes (0.2 seconds)

==== Summary ================================================================
pass tb_uart_lib.tb_uart_rx.test_tvalid_low_at_start      (1.6 seconds)
pass tb_uart_lib.tb_uart_rx.test_receives_one_byte        (0.1 seconds)
pass tb_uart_lib.tb_uart_rx.test_two_bytes_cause_overflow (0.2 seconds)
pass tb_uart_lib.tb_uart_tx.test_send_one_byte            (0.2 seconds)
pass tb_uart_lib.tb_uart_tx.test_send_many_bytes          (0.2 seconds)
=============================================================================
pass 5 of 5
=============================================================================
Total time was 2.4 seconds
Elapsed time was 2.7 seconds
=============================================================================
All passed!

こんな調子で実行される。この指定されたrun.pyは何をしているのかということだが、ソースコードに脚注を付けていくと、

from os.path import join, dirname
from vunit.verilog import VUnit

ui = VUnit.from_argv()

src_path = join(dirname(__file__), "src")    # ソースファイルの場所を指定できるようにする。

uart_lib = ui.add_library("uart_lib")              # UART本体を格納するためのライブラリを定義する
uart_lib.add_source_files(join(src_path, "*.sv"))  # ライブラリにソースコードを挿入する

tb_uart_lib = ui.add_library("tb_uart_lib")                   # UARTのテストパタンを格納するためのライブラリを定義する
tb_uart_lib.add_source_files(join(src_path, "test", "*.sv"))  # UARTのライブラリにソースコードを挿入する。

ui.main()  # テスト実行

テストパタンの中身

テストパタンの中身をチェックしてみる。例えば、 examples/verilog/uart/src/test/tb_uart_tx.sv を見てみよう。いくつかのtaskが定義されていることが分かる。

テストケースとテストスイート

TEST_SUITEはテストスイートを定義する。TEST_SUITEの中には、テストケースを定義している。テストケースは、テストスイート内に複数定義することが出来る。

テストケースはそのままテストを記述している。

   `TEST_SUITE begin
      `TEST_CASE("test_send_one_byte") begin
         send();
         check_all_was_received();
      end
      `TEST_CASE("test_send_many_bytes") begin
         for (int i=0; i<7; i++) begin
            send();
         end
         check_all_was_received();
      end
   end

テストケースでは、いくつかのdefineが使用できる。

  • TEST_SUITE_SETUP : 全てのテストスイート共通のセットアップを行う。
  • TEST_CASE_SETUP : 全てのテストケースの共通のセットアップを行う。
  • CHECK_EQUAL : アサーションを記述する。
  • TEST_CASE_CLEANUP : 全てのテストケース共通で、テスト終了後の処理を行う。
  • TEST_SUITE_CLEANUP : 全てのテストスイート共通で、テスト終了後の処理を行う。
  • WATCHDOG : タイムアウト設定

これでいくつかサンプル作って試してみたいな。

Rocket Chipの足回りを理解する (7. デバッグモジュールによるデータの転送)

前回の続き。Rocket Chipがどのようにしてプログラムをロードするのかを引き続き解析している。riscv-fesvrのdtm.ccを見てその動作を解析している。

write_chunk (uint64_t taddr, size_t len, const void* src)

ターゲットのアドレスtaddrからlenバイト分、srcデータを書き込む。 情報として処理しなければならないのはこの3つだが、これをどのようにして処理するのか。

f:id:msyksphinz:20171004014749p:plain

まずはターゲットアドレスtaddrレジスタs0に転送する。転送するためには転送コマンドを送信する必要があるが、これをどのようにして作っているのかというと、

  • riscv-fesvr/fesvr/dtm.cc
  uint32_t command = AC_ACCESS_REGISTER_TRANSFER |
    AC_ACCESS_REGISTER_WRITE |
    AC_AR_SIZE(xlen) |
    AC_AR_REGNO(S0);

  RUN_AC_OR_DIE(command, prog, 3, data, xlen/(4*8));

RUN_AC_OR_DIEというのはrun_abstract_command()という関数のためのWrapperである。 Abstract Commandというのは何かというとDebug仕様書に書いてあるのだが、Abstract Commandのフォーマットに従って制御される。 Abstract Commandのフォーマットは以下。

f:id:msyksphinz:20171004003513p:plain

  • Access Register : Rocket Coreのレジスタにアクセスする。Write=0, Transfer=1でデータをコアから取得し、Write=1, Transfer=1でデータをコアに転送する。
  • Postexecが設定されている場合、プログラムバッファを実行する。

プログラムバッファ

このPostExecで設定されるプログラムバッファに書き込まれたプログラムを、Rocket Chipは実行することが出来るのだが、この実行できるプログラムはRISC-Vのプログラムそのままに他ならない。 dtm.ccにはdefineデスクリプションにより簡単なプログラムが定義されているが、

  • riscv-fesvr/fesvr/dtm.cc
#define LOAD(xlen, dst, base, imm) \
  (((xlen) == 64 ? 0x00003003 : 0x00002003) \
   | ((dst) << 7) | ((base) << 15) | (uint32_t)ENCODE_ITYPE_IMM(imm))
#define STORE(xlen, src, base, imm) \
  (((xlen) == 64 ? 0x00003023 : 0x00002023) \
   | ((src) << 20) | ((base) << 15) | (uint32_t)ENCODE_STYPE_IMM(imm))
#define JUMP(there, here) (0x6f | (uint32_t)ENCODE_UJTYPE_IMM((there) - (here)))
#define BNE(r1, r2, there, here) (0x1063 | ((r1) << 15) | ((r2) << 20) | (uint32_t)ENCODE_SBTYPE_IMM((there) - (here)))
#define ADDI(dst, src, imm) (0x13 | ((dst) << 7) | ((src) << 15) | (uint32_t)ENCODE_ITYPE_IMM(imm))
#define SRL(dst, src, sh) (0x5033 | ((dst) << 7) | ((src) << 15) | ((sh) << 20))
#define FENCE_I 0x100f
#define EBREAK  0x00100073

このプログラムは、Abstract Commandのプログラムサイズ設定フィールドが1以上に設定されている場合、EBREAKで停止する。

  • read_chunk()
  prog[0] = LOAD(xlen, S1, S0, 0);
  prog[1] = ADDI(S0, S0, xlen/8);
  prog[2] = EBREAK;
  • write_chunk()
  prog[0] = STORE(xlen, S1, S0, 0);
  prog[1] = ADDI(S0, S0, xlen/8);
  prog[2] = EBREAK;

プログラムをデバッグレジスタにロードしては、Rocket Chipを動かしてプログラムをストアするという方式でメモリに書き込みを行っていることが分かる。