FPGA開発日記

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

RISC-VのLinuxブート環境をbuildrootで構築する (5. ブートシーケンスデバッグ)

RISC-VのLinuxブート環境を構築したので、次にこれをRTL環境で実行している。

いくつかの基本的な機能が整ったので、Linuxの起動を試行しながらデバッグをしている。

  • Spikeとの実装を合わせるために、TIME CSRレジスタの実装を削除した。これはメモリマップドIOとなる。
 `include "msrh_csr_def.svh"
@@ -34,6 +32,7 @@ logic w_rd_counter;
 logic w_rd_mcsr_ill_access;
 logic w_rd_scsr_ill_access;
 logic w_rd_fpu_ill_access;
+logic w_rd_ill_address;

 logic [63: 0]                 r_cycle;    // These registers are always 64-bit
 logic [63: 0]                 r_instret;  // These registers are always 64-bit
@@ -151,7 +150,7 @@ xlen_t r_pmpaddr15;
 xlen_t r_stats;

 always_comb begin
-  o_xcpt = 1'b0;
+  w_rd_ill_address = 1'b0;
   case (read_if.addr)
     `SYSREG_ADDR_USTATUS        : read_if.data = r_ustatus;
     `SYSREG_ADDR_UIE            : read_if.data = r_uie;
@@ -168,7 +167,7 @@ always_comb begin
     `SYSREG_ADDR_FRM            : read_if.data = {29'h0, r_frm};
     `SYSREG_ADDR_FCSR           : read_if.data = {24'h0, r_frm, r_fflags};
     `SYSREG_ADDR_CYCLE          : read_if.data = r_cycle  [riscv_pkg::XLEN_W-1: 0];
-    `SYSREG_ADDR_TIME           : read_if.data = r_time   [riscv_pkg::XLEN_W-1: 0];
+    // `SYSREG_ADDR_TIME           : read_if.data = r_time   [riscv_pkg::XLEN_W-1: 0];
     `SYSREG_ADDR_INSTRET        : read_if.data = r_instret[riscv_pkg::XLEN_W-1: 0];
     `SYSREG_ADDR_HPMCOUNTER3    : read_if.data = r_hpmcounter[ 3][riscv_pkg::XLEN_W-1: 0];
     `SYSREG_ADDR_HPMCOUNTER4    : read_if.data = r_hpmcounter[ 4][riscv_pkg::XLEN_W-1: 0];
@@ -201,7 +200,7 @@ always_comb begin
     `SYSREG_ADDR_HPMCOUNTER31   : read_if.data = r_hpmcounter[31][riscv_pkg::XLEN_W-1: 0];
 `ifdef RV32
     `SYSREG_ADDR_CYCLEH         : read_if.data = r_cycle          [63:32];
-    `SYSREG_ADDR_TIMEH          : read_if.data = r_time           [63:32];
+    // `SYSREG_ADDR_TIMEH          : read_if.data = r_time           [63:32];
     `SYSREG_ADDR_INSTRETH       : read_if.data = r_instret        [63:32];
     `SYSREG_ADDR_HPMCOUNTERH3   : read_if.data = r_hpmcounter[ 3][63:32];
     `SYSREG_ADDR_HPMCOUNTERH4   : read_if.data = r_hpmcounter[ 4][63:32];
@@ -363,7 +362,7 @@ always_comb begin
     `SYSREG_ADDR_STATS          : read_if.data = r_stats;
     default : begin
       read_if.data = {riscv_pkg::XLEN_W{1'bx}};
-      o_xcpt = 1'b1;
+      w_rd_ill_address = 1'b1;
     end
   endcase // case (read_if.addr)
 end // always_comb
@@ -647,7 +646,8 @@ assign w_rd_fpu_ill_access  = ((read_if.addr == `SYSREG_ADDR_FFLAGS) |

 assign read_if.resp_error = read_if.valid & (w_rd_mcsr_ill_access | w_rd_scsr_ill_access |
                                              w_rd_counter | w_rd_satp_tvm_1 |
-                                             w_rd_fpu_ill_access);
+                                             w_rd_fpu_ill_access |
+                                             w_rd_ill_address);

ここまでで、ブートシーケンスの初期の段階は成功し、HTIFのメモリマップドI/Oの処理のところで無限ループに入った。 SpikeはHTIFを使っているのでこれでOKだが、実際にRTLで動かすときはちゃんとデバイスを使用しなければならない。これを実装していこう。

Sniperの動作原理をトレースする (3. Performance Modelの解析)

Sniperのログを見ながら、Sniperのサイクル計算モデルを検証してみようと思う。

ROBは1サイクル毎に更新されるので、その状態を観察する。

   Outstanding loads: 1  stores: 0
   [  0]  DONE@+37                        3  LOAD      100c: ld          t0, 24(t0)  {0x1018}
   [  1]  READY@+36                       4  EXEC ( 1) 100c: ld          t0, 24(t0)
   [  2]  DONE@+0                         5  EXEC ( 1) 1010: jr          t0
   [  3]  PREROB                          6  EXEC ( 1) 80000000: mv          ra, zero
   [  4]  PREROB                          7  EXEC ( 1) 80000002: mv          sp, zero
   [  5]  PREROB                          8  EXEC ( 1) 80000004: mv          gp, zero
   [  6]  PREROB                          9  EXEC ( 1) 80000006: mv          tp, zero
   [  7]  PREROB                         10  EXEC ( 1) 80000008: mv          t0, zero
   [  8]  PREROB                         11  EXEC ( 1) 8000000a: mv          t1, zero
   [  9]  PREROB                         12  EXEC ( 1) 8000000c: mv          t2, zero
   [ 10]  PREROB                         13  EXEC ( 1) 8000000e: mv          s0, zero
   [ 11]  PREROB                         14  EXEC ( 1) 80000010: mv          s1, zero
  1. PREROB はROBにまだ入っていない状態を指す。命令はDispatchされたがまだROBにインプットされていないということかな? シミュレーション的に、1命令を実行したときに、まずはROBに入らずにフロントエンドでフェッチ状態となる。

以下の状態だと、サイクル0で命令は10個存在し、すべてフェッチできていない状態。で、272サイクル後に命令キャッシュからのレスポンスが返ってくるというわけで、次に272サイクル目に飛ぶ。

Running cycle 0
-- icache miss(272)
** ROB state @ 0  size(0) total(10)
   Front-end stalled until 272, in I-cache miss
   RS entries: 0
   Outstanding loads: 0  stores: 0
   [  0]  PREROB                          0  EXEC ( 1) 1000: auipc       t0, pc + 0
   [  1]  PREROB                          1  EXEC ( 1) 1004: addi        a1, t0, 32
   [  2]  PREROB                          2  EXEC ( 1) 1008: csrrs       a0, mhartid, zero
   [  3]  PREROB                          3  LOAD      100c: ld          t0, 24(t0)  {0x1018}
   [  4]  PREROB                          4  EXEC ( 1) 100c: ld          t0, 24(t0)
   [  5]  PREROB                          5  EXEC ( 1) 1010: jr          t0
   [  6]  PREROB                          6  EXEC ( 1) 80000000: mv          ra, zero
   [  7]  PREROB                          7  EXEC ( 1) 80000002: mv          sp, zero
   [  8]  PREROB                          8  EXEC ( 1) 80000004: mv          gp, zero
   [  9]  PREROB                          9  EXEC ( 1) 80000006: mv          tp, zero
  1. READYはROBに入って実行可能状態になっている。ただ、命令的には2は1に依存しているし、3はSerializeに実行しなければならない気がするのだがそれは違うのか?

READY@+1 というのは、1サイクル後にREADY状態になることを意味している。

Running cycle 272
-- icache return
DISPATCH  EXEC   (auipc   :0x2   )
DISPATCH  EXEC   (addi    :0x13  )
DISPATCH  EXEC   (csrrs   :0x7e  )
DISPATCH  LOAD   (ld      :0x29  )
DISPATCH  EXEC   (ld      :0x29  )
** ROB state @ 272  size(5) total(10)
   RS entries: 5
   Outstanding loads: 0  stores: 0
   [  0]  READY@+1                        0  EXEC ( 1) 1000: auipc       t0, pc + 0
   [  1]  READY@+1                        1  EXEC ( 1) 1004: addi        a1, t0, 32
   [  2]  READY@+1                        2  EXEC ( 1) 1008: csrrs       a0, mhartid, zero
   [  3]  READY@+1                        3  LOAD      100c: ld          t0, 24(t0)  {0x1018}
DynamicMicroOp::getDependency(0) Length=0
   [  4]  DEPS 3                          4  EXEC ( 1) 100c: ld          t0, 24(t0)

次のサイクルでは、READY状態から完了してDONE状態になっている。

Running cycle 273
DISPATCH  EXEC   (jr      :0x133 )
-- icache miss(297)
ISSUE     EXEC   (auipc   :0x2   )   latency=1
ISSUE     EXEC   (addi    :0x13  )   latency=1
ISSUE     EXEC   (csrrs   :0x7e  )   latency=1
** ROB state @ 273  size(6) total(15)
   Front-end stalled until 570, in I-cache miss
   RS entries: 3
   Outstanding loads: 0  stores: 0
   [  0]  DONE@+2                         0  EXEC ( 1) 1000: auipc       t0, pc + 0
   [  1]  DONE@+2                         1  EXEC ( 1) 1004: addi        a1, t0, 32
   [  2]  DONE@+2                         2  EXEC ( 1) 1008: csrrs       a0, mhartid, zero
   [  3]  READY@+0                        3  LOAD      100c: ld          t0, 24(t0)  {0x1018}
DynamicMicroOp::getDependency(0) Length=0
   [  4]  DEPS 3                          4  EXEC ( 1) 100c: ld          t0, 24(t0)
   [  5]  READY@+1                        5  EXEC ( 1) 1010: jr          t0

次のサイクルでは、最初の命令があと1命令で実行終了し、それに基づいて0x100cのLD命令が動き出す。これはキャッシュミス(というかUCアクセス)で40サイクルかかる計算になっている。

Running cycle 274
ISSUE     LOAD   (ld      :0x29  )   latency=39
ISSUE     EXEC   (jr      :0x133 )   latency=1
** ROB state @ 274  size(6) total(15)
   Front-end stalled until 570, in I-cache miss
   RS entries: 1
   Outstanding loads: 1  stores: 0
   [  0]  DONE@+1                         0  EXEC ( 1) 1000: auipc       t0, pc + 0
   [  1]  DONE@+1                         1  EXEC ( 1) 1004: addi        a1, t0, 32
   [  2]  DONE@+1                         2  EXEC ( 1) 1008: csrrs       a0, mhartid, zero
   [  3]  DONE@+40                        3  LOAD      100c: ld          t0, 24(t0)  {0x1018}
   [  4]  READY@+39                       4  EXEC ( 1) 100c: ld          t0, 24(t0)
   [  5]  DONE@+2                         5  EXEC ( 1) 1010: jr          t0

40サイクル後にLDが完了し、それとともにJR命令でジャンプが行われる。なるほど、意外とシンプルなモデルだ。

RISC-VのLinuxブート環境をbuildrootで構築する (4. ブート用HEXファイルの作り方)

RISC-VのLinuxブート環境を構築したので、次にこれをRTL環境で実行している。

ChipyardやSpikeの環境では、BootROMに、最初のブートシーケンス及びDevice Treeの情報がバイナリ形式で格納されている。 これをどのように作成するのかについてまとめておく。

  1. ブートシーケンスを用意する。今回はRV64用のみ。
.section .text
_init:
    .word       0x00000297 # auipc   t0,0x0
    .word       0x02028593 # addi    a1,t0,24 # 1018 <dtb>
    # la      a1, dtb
        csrr    a0, mhartid

        ld      t0, 24(t0)
        jr      t0
    .word   0
    .word   0x80000000  # start PC
    .word   0x00000000  # start PC
dtb:

ちなみに、AUIPCADDIアセンブリ命令ではなく機械語で挿入したのは、Spikeと完全動作を一致させるために中間変数で使用されるレジスタが変わることを防ぐため。

以下のコマンドでブートシーケンスを含んだbinaryファイルを用意する。

riscv64-unknown-elf-as bootrom.S -o bootrom.o
riscv64-unknown-elf-ld bootrom.o -o bootrom.elf -Tlink.ld
objcopy -I elf64-little  -O binary --only-section .text bootrom.elf bootrom.bin
  1. Device Treeファイルを作成する

テンプレートとなるDevice Tree Fileを用意しておく。

  • dts_skeleton.dts
/dts-v1/;

...
    #size-cells = <0>;
    timebase-frequency = <10000000>;
    CPU0: cpu@0 {
      device_type = "cpu";
      reg = <0>;
      status = "okay";
      compatible = "riscv";
      riscv,isa = "rv|XLEN||EXTENSION|";
      mmu-type = "riscv,|SVMODE|";
      riscv,pmpregions = <16>;

テンプレートの一部を置き換えて、コンフィグレーションにあったDevice Treeファイルを置き換える。

sed 's/|XLEN|/64/g' dts_skelenton.dts | \
        sed 's/|EXTENSION|/imc/g' | \
        sed 's/|SVMODE|/sv39/g' > dts_skelenton_tmp.dts
        dtc -@ -O dtb dts_skelenton_tmp.dts -o dts_rv$(XLEN)$(EXT).dtb
  1. ブート用バイナリの作成

最後に、これをつなぎ合わせてブート用のバイナリを作成する。ダミーファイルを入れているのは自分のRTLシミュレーション環境の問題で、本来は必要ないはず。

cat bootrom.bin dts_rv64imc.dtb dummy.txt > bootrom.img
objcopy -I binary -O verilog --verilog-data-width 16 bootrom.img bootrom.hex

こうして、ブート用のHEXファイルが作成されるので、$readmemhによりROMに格納するわけだ。

RISC-VのLinuxブート環境をbuildrootで構築する (3. RTLでブートローダを実行する)

RISC-VのLinuxブート環境を構築したので、次にこれをRTL環境で実行してみようと思う。

まずは、ブートローダをロードする前のBootROMを作成する必要がある。 SpikeやChipyardでは、BootROMが定義されており、CPUは最初にそこから命令をロードして実行する。

Chipyardの場合は、0x4000にBootROMが存在している。

    L23: soc {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "freechips,rocketchip-unknown-soc", "simple-bus";
        ranges;
        L16: boot-address-reg@4000 {
            reg = <0x4000 0x1000>;
            reg-names = "control";
        };

Spikeの場合は、0x1000にBootROMが存在している。

  boot_rom.reset(new rom_device_t(rom));
  bus.add_device(DEFAULT_RSTVEC, boot_rom.get());

これをRTLシミュレーションで実現しなければならない。まずは外部インタフェースに、L2と並行して仮想的なBootROMを実装した。

localparam WORDS = SIZE / (DATA_W / 8);

logic [DATA_W-1: 0]           r_rom[WORDS];
logic [DATA_W-1: 0]           r_rom_raw[WORDS];

assign o_req_ready = 1'b1;

logic [RD_LAT-1: 0]           r_resp_valid;
logic [DATA_W-1: 0]           r_resp_data[RD_LAT];
logic [TAG_W-1 : 0]           r_resp_tag [RD_LAT];

always_ff @ (posedge i_clk, negedge i_reset_n) begin
  if (!i_reset_n) begin
    r_resp_valid[0] <= 'h0;
    r_resp_data [0] <= 'h0;
    r_resp_tag  [0] <= 'h0;
  end else begin
    r_resp_valid[0] <= i_req_valid;
    r_resp_data [0] <= r_rom[i_req_addr[$clog2(WORDS)-1: $clog2(DATA_W / 8)]];
    r_resp_tag  [0] <= i_req_tag;
  end
end

generate for (genvar p_idx = 1; p_idx < RD_LAT; p_idx ++) begin : rd_loop
  always_ff @ (posedge i_clk, negedge i_reset_n) begin
    if (!i_reset_n) begin
      r_resp_valid[p_idx] <= 'h0;
      r_resp_tag  [p_idx] <= 'h0;
      r_resp_data [p_idx] <= 'h0;
    end else begin
      r_resp_valid[p_idx] <= r_resp_valid[p_idx-1];
      r_resp_tag  [p_idx] <= r_resp_tag  [p_idx-1];
      r_resp_data [p_idx] <= r_resp_data [p_idx-1];
    end
  end // always_ff @ (posedge i_clk, negedge i_reset_n)
end // block: rd_loop
endgenerate

assign o_resp_valid = r_resp_valid[RD_LAT-1];
assign o_resp_tag   = r_resp_tag  [RD_LAT-1];
assign o_resp_data  = r_resp_data [RD_LAT-1];

次にBootROMの中身だが、Spikeのほうはこのようになっている。

  uint32_t reset_vec[reset_vec_size] = {
    0x297,                                      // auipc  t0,0x0
    0x28593 + (reset_vec_size * 4 << 20),       // addi   a1, t0, &dtb
    0xf1402573,                                 // csrr   a0, mhartid
    get_core(0)->get_xlen() == 32 ?
      0x0182a283u :                             // lw     t0,24(t0)
      0x0182b283u,                              // ld     t0,24(t0)
    0x28067,                                    // jr     t0
    0,
    (uint32_t) (start_pc & 0xffffffff),
    (uint32_t) (start_pc >> 32)
  };

最初のアセンブリ命令列の後に、DTBがバイナリ形式で置かれている。 まずはa1(第2引数)にDTBの位置を設定する。 次にa0(第1引数)はコア番号となる。

さらに、t0にエントリポイント(0x8000_0000)を格納してジャンプする。という要領だ。

0x8000_0000から先は当該アプリケーション(Linuxの場合はブートローダ)が走り始めることになる。

これと全く同じ環境を作成して自作のBootROMに格納し、自作CPUを立ち上げてみる。

      1000 :        262 : IPC(recent) = 0.26, IPC(total) = 0.26
      2000 :        598 : IPC(recent) = 0.34, IPC(total) = 0.30
      3000 :        930 : IPC(recent) = 0.33, IPC(total) = 0.31
      4000 :       1264 : IPC(recent) = 0.33, IPC(total) = 0.32
      5000 :       1599 : IPC(recent) = 0.34, IPC(total) = 0.32
      6000 :       1930 : IPC(recent) = 0.33, IPC(total) = 0.32
      7000 :       2266 : IPC(recent) = 0.34, IPC(total) = 0.32
      8000 :       2596 : IPC(recent) = 0.33, IPC(total) = 0.32
      9000 :       2932 : IPC(recent) = 0.34, IPC(total) = 0.33
     10000 :       3262 : IPC(recent) = 0.33, IPC(total) = 0.33
     11000 :       3598 : IPC(recent) = 0.34, IPC(total) = 0.33
     12000 :       3930 : IPC(recent) = 0.33, IPC(total) = 0.33
     13000 :       4264 : IPC(recent) = 0.33, IPC(total) = 0.33
     14000 :       4599 : IPC(recent) = 0.34, IPC(total) = 0.33
     15000 :       4930 : IPC(recent) = 0.33, IPC(total) = 0.33
     16000 :       5266 : IPC(recent) = 0.34, IPC(total) = 0.33
     17000 :       5596 : IPC(recent) = 0.33, IPC(total) = 0.33
     18000 :       5932 : IPC(recent) = 0.34, IPC(total) = 0.33
linux   : ERROR

6000命令くらいのところまで行った。どうもDTBのロード値がおかしいらしい?RTLのロードした値が正しく、Spikeがおかしいように見える。これは解析する必要がありそうだ。

GPR[11](42) <= 0000000000000000
44113 : 6025 : PC=[000000008000c944] (M,06,01) 00354303 lbu     t1, 3(a0)
MR1(0x0000000000001023)=>00000000000000ed
GPR[06](30) <= 00000000000000ed
44119 : 6026 : PC=[000000008000c948] (M,07,01) 00254603 lbu     a2, 2(a0)
MR1(0x0000000000001022)=>00000000000000fe
GPR[12](38) <= 00000000000000fe
44125 : 6027 : PC=[000000008000c94c] (M,08,01) 00754883 lbu     a7, 7(a0)
MR1(0x0000000000001027)=>00000000000000c8
==========================================
Wrong GPR[17](10): RTL = 00000000000000cc, ISS = 00000000000000c8
==========================================
===============================
SIMULATION FINISH : FAIL (CODE=100)
===============================

RISC-VのLinuxブート環境をbuildrootで構築する (2.RTLによるシミュレーション試行)

RISC-VのLinuxブート環境を構築したので、次にこれをRTL環境で実行してみようと思う。

まずは簡単にブートローダから。これはELF形式で格納されているので簡単に実行することができる。

しかし、ロード直後に落ちてしまった。あれえ、なんでこんなところで落ちるんだ?

Set ToHost Addr 80018608
initial_spike opening /home/msyksphinz/work/riscv/linux/riscv64-linux/output/images/fw_jump.elf ...
spike iss done
25650 : 0 : PC=[0000000080000000] (M,00,01) 00050433 add     s0, a0, zero
GPR[08](32) <= 0000000000000000
25650 : 1 : PC=[0000000080000004] (M,00,02) 000584b3 add     s1, a1, zero
==========================================
Wrong GPR[09](64): RTL = 0000000000000000, ISS = 0000000000001020
==========================================
===============================
SIMULATION FINISH : FAIL (CODE=100)
===============================

よくよく見てみると、RTL環境では最初のBootROM (0x1000)を無視しているのだった。これにより、x10とx11にあらかじめ設定された値が使用できなくなっている(x11dtbの場所を示しているアドレスだった)。

$ sed 's/(/DASM(/g' sim_rv64imc_standard/linux/spike.log | spike-dasm
core   0: 3 0x0000000000001000 auipc   t0, 0x0 x 5 0x0000000000001000
core   0: 3 0x0000000000001004 addi    a1, t0, 32 x11 0x0000000000001020
core   0: 3 0x0000000000001008 csrr    a0, mhartid x10 0x0000000000000000
core   0: 3 0x000000000000100c ld      t0, 24(t0) x 5 0x0000000080000000 mem 0x0000000000001018
core   0: 3 0x0000000000001010 jr      t0
core   0: 3 0x0000000080000000 add     s0, a0, zero x 8 0x0000000000000000
core   0: 3 0x0000000080000004 add     s1, a1, zero x 9 0x0000000000001020

fw_jump.elfではこの情報を実際に使用しているためFailとなっているようだった。これは、RTL側もBootROMをまじめに用意しなければならない。シミュレーション環境の変更が必要だ...

RISC-VのLinuxブート環境をbuildrootで構築する

RISC-V向けLinuxのビルド環境として、buildrootを使用したいのだがどうも安定してビルドできる環境の資料がないので、自分で環境を作ることにした。

自分はWSL上で作業しているのだが、環境変数の設定など少し厄介なところがあるので、せっかくなのでDockerfile上に構築することにした。

https://github.com/msyksphinz-self/riscv-buildroot-linux

https://hub.docker.com/repository/docker/msyksphinz/buildroot

以下のようなフローでbuildrootの構築はできる。ポイントはbuildrootのダウンロードとvirt向けのMakefileの構築だ。

FROM ubuntu:22.04

# -----------------------------
# Ubuntu Package Update
# -----------------------------
RUN apt update

RUN apt install -y git python3 make autoconf g++ device-tree-compiler pkg-config

RUN apt install -y libglib2.0-dev
RUN apt install -y libpixman-1-dev
RUN apt install -y ninja-build

WORKDIR /work-buildroot/

# -------------------
# Build QEMU Package
# -------------------

WORKDIR /work-buildroot/
RUN git clone https://github.com/qemu/qemu
WORKDIR /work-buildroot/qemu
RUN ./configure --target-list=riscv64-softmmu
RUN make -j $(nproc)
RUN make install

# ----------------
# Buildroot Build
# ----------------
WORKDIR /work-buildroot/
RUN git clone https://github.com/buildroot/buildroot.git
WORKDIR /work-buildroot/buildroot/
RUN make qemu_riscv64_virt_defconfig
RUN apt install -y file wget cpio unzip rsync bc
RUN make -j 10

これで、/work/buildroot/output/Linuxビルド用のファイルが生成されるが、Docker内で構築しているためホスト上で実行することができない。 よって、以下のスクリプトで生成物を回収することにした。

DOCKER_NAME := $(shell bash -c 'echo $$RANDOM')
DOCKER_ID := $(shell docker ps -aqf "name=$(DOCKER_NAME)")

copy:
        docker cp $(DOCKER_ID):/work-buildroot/buildroot/output .
        docker kill $(DOCKER_NAME)

これで、/work-buildroot/buildroot/output/内の生成物を回収することができる。 ホストマシン上でQEMUを使ってbuildrootのLinuxを実行することができるようになった。

run-qemu:
        ./qemu/riscv64-softmmu/qemu-system-riscv64 \
                -bios ./output/images/fw_jump.elf \
                -nographic -machine virt -kernel ./output/images/Image \
                -append "root=/dev/vda ro console=ttyS0" \
                -drive file=./output/images/rootfs.ext2,format=raw,id=hd0 -device virtio-blk-device,drive=hd0 \
                -netdev user,id=net0 -device virtio-net-device,netdev=net0
$ make run-qemu
OpenSBI v0.9
   ____                    _____ ____ _____
  / __ \                  / ____|  _ \_   _|
 | |  | |_ __   ___ _ __ | (___ | |_) || |
 | |  | | '_ \ / _ \ '_ \ \___ \|  _ < | |
 | |__| | |_) |  __/ | | |____) | |_) || |_
  \____/| .__/ \___|_| |_|_____/|____/_____|
        | |
        |_|

Platform Name             : riscv-virtio,qemu
Platform Features         : timer,mfdeleg
Platform HART Count       : 1
Firmware Base             : 0x80000000
Firmware Size             : 124 KB
Runtime SBI Version       : 0.2

Domain0 Name              : root
Domain0 Boot HART         : 0
Domain0 HARTs             : 0*
Domain0 Region00          : 0x0000000080000000-0x000000008001ffff ()
Domain0 Region01          : 0x0000000000000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address      : 0x0000000080200000
Domain0 Next Arg1         : 0x0000000082200000
Domain0 Next Mode         : S-mode
Domain0 SysReset          : yes

Boot HART ID              : 0
Boot HART Domain          : root
Boot HART ISA             : rv64imafdcsu
Boot HART Features        : scounteren,mcounteren,time
Boot HART PMP Count       : 16
Boot HART PMP Granularity : 4
Boot HART PMP Address Bits: 54
Boot HART MHPM Count      : 0
Boot HART MHPM Count      : 0
Boot HART MIDELEG         : 0x0000000000000222
Boot HART MEDELEG         : 0x000000000000b109
[    0.000000] Linux version 5.15.43 (root@buildkitsandbox) (riscv64-buildroot-linux-gnu-gcc.br_real (Buildroot 2022.08-353-geedd93f010) 11.3.0, GNU ld (GNU Binutils) 2.38) #1 SMP Sun Sep 25 06:40:33 UTC 2022
[    0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000
[    0.000000] Machine model: riscv-virtio,qemu
[    0.000000] efi: UEFI not found.
[    0.000000] Zone ranges:
[    0.000000]   DMA32    [mem 0x0000000080200000-0x0000000087ffffff]
...
Starting network: udhcpc: started, v1.35.0
udhcpc: broadcasting discover
udhcpc: broadcasting select for 10.0.2.15, server 10.0.2.2
udhcpc: lease of 10.0.2.15 obtained from 10.0.2.2, lease time 86400
deleting routers
adding dns 10.0.2.3
OK

Welcome to Buildroot
buildroot login:
...

RISC-VのコンプライアンステストフレームワークRISCOFを試す (1. インストールとサンプル試行)

RISCOFというのは、RISC-V Compilance Flameworkの略で、RISC-Vアーキテクチャアセンブリテストを使用して、標準のRISC-Vリファレンスモデルに対するハードウェア・ソフトウェアのテストを行うためのPythonベースのフレームワークとなっている。

まずは、インストールを行ってみる。Pythonベースのフレームワークなので、pip3を使用してインストールする。

参考にしたのは以下のサイトである。

https://riscof.readthedocs.io/en/stable/index.html

$ cd work/riscv
$ pip3 install git+https://github.com/riscv/riscof.git

インストール後にコマンドを確認してみる。

$ riscof
Usage: riscof [OPTIONS] COMMAND [ARGS]...

Options:
  --version                       Show the version and exit.
  -v, --verbose [info|error|debug]
                                  Set verbose level
  --help                          Show this message and exit.

Commands:
  arch-test     Setup and maintenance for Architectural TestSuite.
  coverage      Run the tests on DUT and reference and compare signatures
  gendb         Generate Database for the Suite.
  run           Run the tests on DUT and reference and compare signatures
  setup         Initiate Setup for riscof.
  testlist      Generate the test list for the given DUT and suite.
  validateyaml  Validate the Input YAMLs using riscv-config.

大まかな手順であるが、

  1. riscof setupで必要なコンフィグレーションを生成する
  2. プラットフォームに応じてコンフィグレーションを変更する
  3. riscof validateyamlで変更したコンフィグレーションのチェックを行う
  4. riscv-arch-testをダウンロードしてテストリストを取得する
  5. riscof testlistを実行してプラットフォームに必要なテストリストファイルを生成する
  6. riscof runを実行してテストを実行する

1. risco setupによるコンフィグレーションファイルの生成

risof setupによりコンフィグレーションファイルおよびプラットフォームファイルを生成する。以下の例では、プラットフォーム(DUT)名をmsyksphinz_cpuとしている。

ちなみに、後でわかったのだが、この時にDUTファイル名はハイフンなどのPython上でクラス名にできない文字は入れてはいけない。

$ riscof setup --dutname=msyksphinz_cpu
    INFO | ****** RISCOF: RISC-V Architectural Test Framework 1.25.1 *******
    INFO | using riscv_isac version : 0.15.0
    INFO | using riscv_config version : 3.2.0
    INFO | Setting up sample plugin requirements [Old files will be overwritten]
    INFO | Creating sample Plugin directory for [DUT]: msyksphinz_cpu
    INFO | Creating sample Plugin directory for [REF]: sail_cSim
    INFO | Creating Sample Config File
    INFO | **NOTE**: Please update the paths of the reference and plugins in the config.ini file

これにより以下のコンフィグレーションファイルが生成されている。

$ tree .
.
├── config.ini
├── msyksphinz_cpu
│   ├── __pycache__
│   │   └── riscof_model.cpython-38.pyc
│   ├── env
│   │   ├── link.ld
│   │   └── model_test.h
│   ├── msyksphinz_cpu_isa.yaml
│   ├── msyksphinz_cpu_platform.yaml
│   └── riscof_msyksphinz_cpu.py
└── sail_cSim
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-38.pyc
    │   └── riscof_sail_cSim.cpython-38.pyc
    ├── env
    │   ├── link.ld
    │   └── model_test.h
    └── riscof_sail_cSim.py

6 directories, 13 files

ここでチェックしなければならないのは、msyksphinz_cpu/msykshpinz-cpu_isa.yamlmsyksphinz_cpu/msyksphinz_cpu_platform.yamlであろう。

  • msyksphinz_cpu/msyksphinz_cpu_isa.yaml
hart_ids: [0]
hart0:
  ISA: RV32IMCZicsr_Zifencei
  physical_addr_sz: 32
  User_Spec_Version: '2.3'
  supported_xlen: [32]
  misa:
   reset-val: 0x40001104
   rv32:
     accessible: true
     mxl:
       implemented: true
       type:
           warl:
              dependency_fields: []
              legal:
                - mxl[1:0] in [0x1]
              wr_illegal:
                - Unchanged
     extensions:
       implemented: true
       type:
           warl:
              dependency_fields: []
              legal:
                - extensions[25:0] bitmask [0x0001104, 0x0000000]
              wr_illegal:
                - Unchanged
  • msyksphinz_cpu/msyksphinz_cpu_platform.yaml
mtime:
  implemented: true
  address: 0xbff8
mtimecmp:
  implemented: true
  address: 0x4000
nmi:
  label: nmi_vector
reset:
  label: reset_vector

riscof validateyamlでコンフィグレーションのチェックを行う

riscof validateyamlコマンドで、現在のコンフィグレーションファイルの整合性を確認する。

$ riscof validateyaml --config=config.ini
    INFO | ****** RISCOF: RISC-V Architectural Test Framework 1.25.1 *******
    INFO | using riscv_isac version : 0.15.0
    INFO | using riscv_config version : 3.2.0
    INFO | Reading configuration from: /home/msyksphinz/work/riscv/riscof/config.ini
    INFO | Preparing Models
    INFO | Input-ISA file
    INFO | Loading input file: /home/msyksphinz/work/riscv/riscof/msyksphinz_cpu/msyksphinz_cpu_isa.yaml
    INFO | Load Schema /home/msyksphinz/.local/lib/python3.8/site-packages/riscv_config/schemas/schema_isa.yaml
    INFO | Processing Hart: hart0
    INFO | Initiating Validation
    INFO | No errors for Hart: 0 :)
    INFO | Initiating WARL legality checks.
    INFO | Initiating post processing and reset value checks.
    INFO | Performing Checks on PMP CSRs
    INFO | Dumping out Normalized Checked YAML: /home/msyksphinz/work/riscv/riscof/riscof_work/msyksphinz_cpu_isa_checked.yaml
    INFO | Input-Platform file
    INFO | Loading input file: /home/msyksphinz/work/riscv/riscof/msyksphinz_cpu/msyksphinz_cpu_platform.yaml
    INFO | Load Schema /home/msyksphinz/.local/lib/python3.8/site-packages/riscv_config/schemas/schema_platform.yaml
    INFO | Initiating Validation
    INFO | No Syntax errors in Input Platform Yaml. :)
    INFO | Dumping out Normalized Checked YAML: /home/msyksphinz/work/riscv/riscof/riscof_work/msyksphinz_cpu_platform_checked.yaml

riscv-arch-testをダウンロードしてテストリストを取得する

riscv-arch-testをダウンロードしてテストセットを用意する。

$ riscof --verbose info arch-test --clone
    INFO | ****** RISCOF: RISC-V Architectural Test Framework 1.25.1 *******
    INFO | using riscv_isac version : 0.15.0
    INFO | using riscv_config version : 3.2.0
    INFO | Clonning repository at /home/msyksphinz/work/riscv/riscof/riscv-arch-test
    INFO | Clonned version 3.4.1 of the repository with commit hash da638bafbc6d69f75ffc09769cfe7e3a45b41a3a

これで、同じディレクトリにriscv-arch-testがダウンロードされる。

riscof testlistでテストリストファイルを生成する

次に、このテストリストからコンフィグレーションに基づいてテストリストを作成する。

$ riscof testlist --config=config.ini --suite=riscv-arch-test/riscv-test-suite/ --env=riscv-arch-test/riscv-test-suite/env
    INFO | ****** RISCOF: RISC-V Architectural Test Framework 1.25.1 *******
    INFO | using riscv_isac version : 0.15.0
    INFO | using riscv_config version : 3.2.0
    INFO | Generating database for suite: /home/msyksphinz/work/riscv/riscof/riscv-arch-test/riscv-test-suite
    INFO | Database File Generated: /home/msyksphinz/work/riscv/riscof/riscof_work/database.yaml
    INFO | Env path set to/home/msyksphinz/work/riscv/riscof/riscv-arch-test/riscv-test-suite/env
    INFO | Reading configuration from: /home/msyksphinz/work/riscv/riscof/config.ini
    INFO | Preparing Models
    INFO | Input-ISA file
    INFO | Loading input file: /home/msyksphinz/work/riscv/riscof/msyksphinz_cpu/msyksphinz_cpu_isa.yaml
    INFO | Load Schema /home/msyksphinz/.local/lib/python3.8/site-packages/riscv_config/schemas/schema_isa.yaml
    INFO | Processing Hart: hart0
    INFO | Initiating Validation
    INFO | No errors for Hart: 0 :)
    INFO | Initiating WARL legality checks.
    INFO | Initiating post processing and reset value checks.
    INFO | Performing Checks on PMP CSRs
    INFO | Dumping out Normalized Checked YAML: /home/msyksphinz/work/riscv/riscof/riscof_work/msyksphinz_cpu_isa_checked.yaml
    INFO | Input-Platform file
    INFO | Loading input file: /home/msyksphinz/work/riscv/riscof/msyksphinz_cpu/msyksphinz_cpu_platform.yaml
    INFO | Load Schema /home/msyksphinz/.local/lib/python3.8/site-packages/riscv_config/schemas/schema_platform.yaml
    INFO | Initiating Validation
    INFO | No Syntax errors in Input Platform Yaml. :)
    INFO | Dumping out Normalized Checked YAML: /home/msyksphinz/work/riscv/riscof/riscof_work/msyksphinz_cpu_platform_checked.yaml
    INFO | Selecting Tests.

これで、riscof_work/test_list.yamlにテストリストが生成されている。

# testlist generated on 2022-09-25 06:29 GMT
/home/msyksphinz/work/riscv/riscof/riscv-arch-test/riscv-test-suite/rv32i_m/C/src/cadd-01.S:
  commit_id: 6ba8712973dade81eaf1182d1050e5799a7ef2a0
  work_dir: /home/msyksphinz/work/riscv/riscof/riscof_work/rv32i_m/C/src/cadd-01.S
  macros:
  - TEST_CASE_1=True
  - XLEN=32
  isa: RV32IC
  coverage_labels:
  - cadd
  test_path: /home/msyksphinz/work/riscv/riscof/riscv-arch-test/riscv-test-suite/rv32i_m/C/src/cadd-01.S
/home/msyksphinz/work/riscv/riscof/riscv-arch-test/riscv-test-suite/rv32i_m/C/src/caddi-01.S:
  commit_id: 6ba8712973dade81eaf1182d1050e5799a7ef2a0
  work_dir: /home/msyksphinz/work/riscv/riscof/riscof_work/rv32i_m/C/src/caddi-01.S
  macros:
  - TEST_CASE_1=True
  - XLEN=32
  isa: RV32IC
  coverage_labels:

riscof runを実行してテストを実行する

最後に、テストを実行する。

$ riscof run --config=config.ini --suite=riscv-arch-test/riscv-test-suite/ --env=riscv-arch-test/riscv-test-suite/env
INFO | ****** RISCOF: RISC-V Architectural Test Framework 1.25.1 *******
    INFO | using riscv_isac version : 0.15.0
    INFO | using riscv_config version : 3.2.0
    INFO | Reading configuration from: /home/msyksphinz/work/riscv/riscof/config.ini
    INFO | Preparing Models
    INFO | Input-ISA file
    INFO | Loading input file: /home/msyksphinz/work/riscv/riscof/msyksphinz_cpu/msyksphinz_cpu_isa.yaml
    INFO | Load Schema /home/msyksphinz/.local/lib/python3.8/site-packages/riscv_config/schemas/schema_isa.yaml
    INFO | Processing Hart: hart0
    INFO | Initiating Validation
    INFO | No errors for Hart: 0 :)
    INFO | Initiating WARL legality checks.
    INFO | Initiating post processing and reset value checks.
    INFO | Performing Checks on PMP CSRs
    INFO | Dumping out Normalized Checked YAML: /home/msyksphinz/work/riscv/riscof/riscof_work/msyksphinz_cpu_isa_checked.yaml
    INFO | Input-Platform file
    INFO | Loading input file: /home/msyksphinz/work/riscv/riscof/msyksphinz_cpu/msyksphinz_cpu_platform.yaml
    INFO | Load Schema /home/msyksphinz/.local/lib/python3.8/site-packages/riscv_config/schemas/schema_platform.yaml
    INFO | Initiating Validation
    INFO | No Syntax errors in Input Platform Yaml. :)
    INFO | Dumping out Normalized Checked YAML: /home/msyksphinz/work/riscv/riscof/riscof_work/msyksphinz_cpu_platform_checked.yaml
    INFO | Generating database for suite: /home/msyksphinz/work/riscv/riscof/riscv-arch-test/riscv-test-suite
    INFO | Database File Generated: /home/msyksphinz/work/riscv/riscof/riscof_work/database.yaml
    INFO | Env path set to/home/msyksphinz/work/riscv/riscof/riscv-arch-test/riscv-test-suite/env
    INFO | Running Build for DUT
    INFO | Running Build for Reference
   ERROR | riscv32-unknown-elf-objdump: executable not found. Please check environment setup.

おっと、ツールチェインをインストールしていないので最後に怒られてしまった。

ツールチェインをインストールして再実行すると、今度は以下のようなエラーが生成された。これは引き続き調査する。

ERROR | riscv_sim_RV32: executable not found. Please check environment setup.