FPGA開発日記

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

自作RISC-V OoOコアでDhrystoneが完走した

ちまちまと自作RISC-Vコアを実装している。riscv-testsがかなり通っているので、Dhrystoneを実行することにした。

実はDhrystoneは少し前に動かしていたのだけれども、L1Dキャッシュのデータリプレースの部分になかなか難しいバグがあり、難しくなっていた部分を一気に書き換えてバグを全面的に潰した。 その結果、Dhrystoneが完走できるようになった。

riscv-testsにはすでにDhrystoneなどのベンチマークが備わっているが、現在の実装では上手く動かすことが出来ない。 いくつかの原因があって、

  • FPUをサポートしていないので、GCCコンパイルオプションを変更する必要がある
  • tohost / fromhostを正しく実装していないので、printf()の実装をいくつか変更する必要がある

まずはriscv-tests/benchmarks/の中で、FPUが必須のベンチマークプログラムを外して、コンパイルオプションを変更する。

diff --git a/benchmarks/Makefile b/benchmarks/Makefile
index c9469e2..44316be 100644
--- a/benchmarks/Makefile
+++ b/benchmarks/Makefile
@@ -24,20 +24,21 @@ bmarks = \
        towers \
        vvadd \
        multiply \
-       mm \
        dhrystone \
-       spmv \
-       mt-vvadd \
-       mt-matmul \
        pmp \

+#      mm \
+#      spmv \
+#      mt-vvadd \
+#      mt-matmul \
+
 #--------------------------------------------------------------------
 # Build rules
 #--------------------------------------------------------------------

 RISCV_PREFIX ?= riscv$(XLEN)-unknown-elf-
 RISCV_GCC ?= $(RISCV_PREFIX)gcc
-RISCV_GCC_OPTS ?= -DPREALLOCATE=1 -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf -fno-tree-loop-distribute-patterns
+RISCV_GCC_OPTS ?= -march=rv$(XLEN)imc -mabi=lp$(XLEN) -DPREALLOCATE=1 -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf -fno-tree-loop-distribute-patterns
 RISCV_LINK ?= $(RISCV_GCC) -T $(src_dir)/common/test.ld $(incs)
 RISCV_LINK_OPTS ?= -static -nostdlib -nostartfiles -lm -lgcc -T $(src_dir)/common/test.ld
 RISCV_OBJDUMP ?= $(RISCV_PREFIX)objdump --disassemble-all --disassemble-zeroes --section=.text --section=.text.startup --section=.text.init --section=.data

次に、syscalls.cを変更してputchar()を実行した後にfromhostの状態変更を待たずにすぐに戻るように変更する。

diff --git a/benchmarks/common/syscalls.c b/benchmarks/common/syscalls.c
index 39547b3..f3f8836 100644
--- a/benchmarks/common/syscalls.c
+++ b/benchmarks/common/syscalls.c
@@ -25,8 +25,8 @@ static uintptr_t syscall(uintptr_t which, uint64_t arg0, uint64_t arg1, uint64_t
   __sync_synchronize();

   tohost = (uintptr_t)magic_mem;
-  while (fromhost == 0)
-    ;
+  // while (fromhost == 0)
+  //   ;
   fromhost = 0;

   __sync_synchronize();

この変更したことで、Dhrystoneが完走できた。

1906722 : 201307 : PC=[000000008000264e] (20,02) 0017e793 ori     a5, a5, 1
GPR[15](178) <= 0000000000000001
1906722 : 201308 : PC=[0000000080002652] (20,04) fffff717 auipc   a4, 0xfffff
GPR[14](60) <= 0000000080001652
1906722 : 201309 : PC=[0000000080002656] (20,08) 9af73723 sd      a5, -1618(a4)
MW8(0x0000000080001000)=>0000000000000001
1906722 : 201310 : PC=[000000008000265a] (20,10) 0000a001 c.j     pc + 0
GPR[00](0) <= 0000000000000000
1906738 : L1D Stq Store : 80001000(00) : ______________________________________________________00000000_00000001
===============================
SIMULATION FINISH : PASS
===============================
f:id:msyksphinz:20210921235632p:plain

ただし分岐予測が全く実装できていないため、性能については全くダメ... これは今後の実装課題だな。

RISC-V Vector Extension v1.0がリリースされた

これまでのものはv1.0のRC1だったりRC2だったりするけど、いよいよv1.0正式バージョンについてのPublic Review版がリリースされた。

一応差分をチェックしてみたが、本文内の曖昧の所を明文化したようなところが多い。 以下にまとめたのはRC1→RC2からのさらに変更点なので、v1.0はRC2からの以下のさらに変更点が加えられたことになる。

github.com


  • いくつかの文章の言い回しの改善。デザインに依存する項目について追加。
  • vpopc.mvcpop.mに名前変更。スカラ命令との統一性のため。エイリアスとして古い名前はアセンブラに残す。
  • マスク論理命令、vmsbf.m, vmsif.m, vmsof.m マスク操作命令では結果によてすべてのマスクレジスタへの書き込みを許す (つねにTail-agnosticとなる)
  • ベクトル長拡張 "Zvl*"を追加
  • mstatus.VSフィールドへの書き込み操作を明記し、mstatus.SDとの相互作用について追記。
  • ハイパーバイザー化における動作について定義を追加。
  • vtypeにおける不正な値のチェックについて明確化。
  • 呼び出し規約についての項目を追加。例題を理解するためのプレースホルダとしてのみ設置し、RISC-VのpsABIはこれらの項目を含むように拡張される。
  • vxsatビットのはCSRの0ビット目であり、上位のビットは0が書き込まれるべきであることを追加。
  • vxrmフィールドはCSRの下位の2ビットであり、上位のビットは0が書き込まれるべきであることを追加。
  • 1つの要素が複数のベクトルレジスタへの書き込みを行う場合(ELEN > VLEN)の場合のオプションについて説明を簡素化し、この時点ではこの方式は提案されていない。
  • 実装ではEEWの幅が標準的なロードとストア命令においてサポートされていない場合では実装が不正命令例外を発生するべきであるということを追加。
  • セグメントロードストア命令のためのZvlsseg拡張名を削除し、標準的なすべてのベクトル拡張に搭載する必要がある。
  • 全体レジスタロードストア命令においてnfはNFIELDと同様にエンコードされることを明確化。
  • レジスタオーバラップの制約を違反した命令は予約済みであることを明文化。
  • Zfinx/Zdinx/Zhinx拡張がベクトル命令に搭載された場合の浮動小数点スカラ値の扱いについて注記を追加。
  • 以前のアセンブラニーモニックであるvmandnotvmornotvmandnvmornに変更され、同じ動きをする。古いvmandnotvmornotニーモニックアセンブラエイリアスとして残される。
  • 標準ベクトル拡張をサポートした実装ではmisa.vが設定されることを明文化。

殆どの項目は大きな変更がある訳では無いが、Zvl*についてはVLENの最低長を示しているらしい。これそんなに必要あるかな?

Extension Minimum VLEN
Zvl32b 32
Zvl64b 64
Zvl128b 128
Zvl256b 256
Zvl512b 512
Zvl1024b 1024

リングフィットアドベンチャー本編をクリアした

春辺りからリングフィットアドベンチャーを毎日コツコツやっていたのだが、ついに本編を全クリアした。長かった...

全クリアまで100日以上かかっている。地道にやっていったがついにコンプリートだ。

f:id:msyksphinz:20210920222928j:plain

元々のモチベーションは、コロナで外で運動とかできなくなったということ。普段は結構マラソンと化していたのだが、 コロナであまりできなくなってしまった。 そこでちょうど電気屋さんでNintendo Switchを手に入れて、物は試しと思ってリングフィットアドベンチャーを購入してみたのだった。

ちなみにSwitchはリングフィット以外で全く活躍していない(ちょっと桃鉄をやってみたくらい)。ひたすらリングフィット専用マシンとなっていた。

このゲームは運動負荷を設定できるのだが、たしか極めて標準的(19だったかな?)な設定でやっていた気がする。まあ結果的にこれくらいでちょうど良かったかも。

ゲーム自体の感想としては、定期的に細かく運動できるし、1日に10~20分程度でできるので非常にやりやすい。 私は退社してからお風呂に入るまで10~20分くらいを毎日やっていた。まあ本当に毎日続くものではなくて、夏場にひどい風邪をひいたときは2週間くらいはストップというのもあるけど、 結果的に再開してコンプリートまで行けた。

で、実際のところの効果だが、100日以上やって本当に効果があったかというと、それは分からない。 そもそも始める前に体重とか計っていなかったので完了したときにどれだけ体重が変化したのかは分からない。 それは少し後悔。

これからもちょくちょくやっていきたいと思う(まだ全ステージのコースを100%クリアしたわけじゃないし)。 それと次はフィットボクシングをやってみたいかな。後は(だいぶ後発だが)マリオカート

RISC-V向けLinuxをビルドするための確認 (2. Dockerで環境構築)

RISC-V向けLinuxのビルドについて少し確認したいと思っていろいろと調査している。結局WSLの上ではビルドが難しそうなので、WSL2にDockerをインストールしてこの上で動かすことにした。

Dockerでfreedom-u-sdkを構築するためにDockerfileを書いている。

FROM ubuntu:20.04

ENV RISCV=/opt/riscv
ENV PATH=$RISCV/bin:$PATH
ENV MAKEFLAGS=-j4

WORKDIR $RISCV

RUN apt update && \
    apt install -y tzdata && \
    apt install -y git autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev

RUN git clone --single-branch https://github.com/riscv/riscv-gnu-toolchain.git && \
cd riscv-gnu-toolchain && \
git submodule update --init --recursive

RUN cd riscv-gnu-toolchain && mkdir build && \
cd build && \
../configure --prefix=${RISCV} --enable-multilib && \
make linux

WORKDIR /root

RUN git clone https://github.com/sifive/freedom-u-sdk.git -b v1_0 --recurse-submodules
RUN cd freedom-u-sdk && \
make

ハマったのはriscv-gnu-toolchainのビルドにおいてmake linuxではなくmakeとしてしまい、riscv64-unknown-elfが作られてしまいLinuxのビルド時にこけてしまったこと。 これで再度ビルドをやり直した。すると以下のエラーだ。

#12 14.23 make[2]: Leaving directory '/root/freedom-u-sdk/buildroot'
#12 14.31 You must install 'wget' on your build machine
#12 14.32 You must install 'cpio' on your build machine
#12 14.32 You must install 'python' on your build machine
#12 14.32 You must install 'unzip' on your build machine
#12 14.32 You must install 'rsync' on your build machine
#12 14.32 make[1]: *** [support/dependencies/dependencies.mk:24: core-dependencies] Error 1
#12 14.33 make[1]: Leaving directory '/root/freedom-u-sdk/buildroot'
#12 14.33 make: *** [Makefile:90: /root/freedom-u-sdk/work/buildroot_initramfs/images/rootfs.tar] Error 2

またやり直しだ...

Rocket-ChipのPMA検出回路を自作CPUに実装する

前回、Rocket-ChipがどのようにPMAをていぎしているのか分かったので、この構造をそのまま自作CPUに取り込みたいと思う。

前回作成した表をもとに、JSON形式でPMAの定義を作成し、これを自動的にSystemVerilog形式に落とし込むスクリプトを作った。 元となるJSONファイルはこんな感じで定義した。

[
        {
        "base": "0x0000_0000",
        "size": "0x0000_1000",
        "attr": {"R": 1, "W": 1, "X": 1, "A": 1, "C": 0},
        "comment": "Debug Controller"
    },
    {
                "base": "0x0000_3000",
        "size": "0x0000_1000",
        "attr": {"R": 1, "W": 1, "X": 1, "A": 1, "C": 0},
        "comment": "error device"
    },
    {
                "base": "0x0000_4000",
        "size": "0x0000_1000",
        "attr": {"R": 1, "W": 1, "A": 1, "X": 0, "C": 0},
        "comment":"boot ROM"
    },
    {
                "base": "0x0001_0000",
        "size": "0x0001_0000",
...

これをスクリプトで読み込ませると、こんな感じのSystemVerilogファイルを吐き出すようにした。

module pma_map (
  input logic [riscv_pkg::PADDR_W-1: 0] i_pa
  output logic o_map_hit,
  output map_attr_t o_mapt_attr_hit
);


localparam MAP_TABLE_SIZE = 11;
logic [MAP_TABLE_SIZE-1: 0] w_hit_addr;
map_attr_t w_map_attr[MAP_TABLE_SIZE];
assign w_hit_addr[ 0] = (i_pa & 'hfffffffffff000) = 'h00000000000000;  // Address Region : 0 - fff
assign w_map_attr[ 0].r = 1'b1;
assign w_map_attr[ 0].w = 1'b1;
assign w_map_attr[ 0].x = 1'b1;
assign w_map_attr[ 0].a = 1'b1;
assign w_map_attr[ 0].c = 1'b0;
assign w_hit_addr[ 1] = (i_pa & 'hfffffffffff000) = 'h00000000002000;  // Address Region : 3000 - 3fff
assign w_map_attr[ 1].r = 1'b1;
assign w_map_attr[ 1].w = 1'b1;
assign w_map_attr[ 1].x = 1'b1;
assign w_map_attr[ 1].a = 1'b1;
/* ... 途中省略 ... */
assign w_map_attr[ 9].x = 1'b0;
assign w_map_attr[ 9].a = 1'b1;
assign w_map_attr[ 9].c = 1'b0;
assign w_hit_addr[10] = (i_pa & 'hfffffff0000000) = 'h00000080000000;  // Address Region : 80000000 - 8fffffff
assign w_map_attr[10].r = 1'b1;
assign w_map_attr[10].w = 1'b1;
assign w_map_attr[10].x = 1'b1;
assign w_map_attr[10].a = 1'b1;
assign w_map_attr[10].c = 1'b1;


assign o_map_hit = |w_hit_addr;
always_comb begin
case (w_hit_addr)
  11'b00000000001 : o_map_attr = w_map_attr[0];
  11'b00000000010 : o_map_attr = w_map_attr[1];
  11'b00000000100 : o_map_attr = w_map_attr[2];
  11'b00000001000 : o_map_attr = w_map_attr[3];
  11'b00000010000 : o_map_attr = w_map_attr[4];
  11'b00000100000 : o_map_attr = w_map_attr[5];
  11'b00001000000 : o_map_attr = w_map_attr[6];
  11'b00010000000 : o_map_attr = w_map_attr[7];
  11'b00100000000 : o_map_attr = w_map_attr[8];
  11'b01000000000 : o_map_attr = w_map_attr[9];
  11'b10000000000 : o_map_attr = w_map_attr[10];
  default   : o_map_attr = 'h0;
endcase
end

endmodule

これを今度はTLBに繋げて、Memory Access例外を通知するように実装してみる。

ChipyardのRocket-ChipにおけるPMA構成調査

自作CPUにPMA (Physical Memory Attributes) を実装するために、まずは既存のRISC-V実装がどのようなメモリマップ構成になっているのかを調査した。 対象としたのはChipyard上で構成できるRocket-Chipだ。Rocket-ChipのVerilogファイルを生成すると同時にメモリマップの情報を持つJSONファイルも生成されるので、これを参考に表を作ってみた。

JSONファイルはめちゃめちゃ崩れているのでFormatterで整理する。

  • chipyard.TestHarness.RocketConfig.json
{"#address-cells":[1],"#size-cells":[1],"aliases":{"serial0":["&/soc/serial@54000000"]},"compatible":["freechips,rocketchip-unknown-dev"],"cpus":{"#address-cells":[1],"#size-cells":[0],"cpu@0":{"clock-frequency":[0],"compatible":["sifive,rocket0","riscv"],"d-cache-block-size":[64],"d-cache-sets":[64],"d-cache-size":[16384],"d-tlb-sets":[1],"d-tlb-size":[32],"device_type":["cpu"],"hardware-exec-breakpoint-count":[1],"i-cache-block-size":[64],"i-cache-sets":[64],"i-cache-size":[16384],"i-tlb-sets":[1],"i-tlb-size":[32],"interrupt-controller":{"#interrupt-cells":[1],"compatible":["riscv,cpu-intc"],"interrupt-controller":[]},"mmu-type":["riscv,sv39"],"next-level-cache":["&/soc/cache-controller@2010000"],"reg":[{"base":0,"size":1,"r":false,"w":false,"x":false,"c":false,"a":false}],"riscv,isa":["rv64imafdc"],"riscv,pmpgranularity":[4],"riscv,pmpregions":[8],"status":["okay"],"timebase-frequency":[1000000],"tlb-split":[]}},"htif":{"compatible":["ucb,htif0"]},"memory@80000000":{"device_type":["memory"],"reg":[{"base":2147483648,"size":268435456,"r":true,"w":true,"x":true,"c":true,"a":true}]},"model":["freechips,rocketchip-unknown"],"soc":{"#address-cells":[1],"#size-cells":[1],"boot-address-reg@4000":{"reg":[{"base":16384,"size":4096,"r":true,"w":true,"x":false,"c"

Formatterで処理した後でメモリマップを表にまとめてみる。

ベースアドレス サイズ アクセス権限 説明
0x0000_0000 0x0000_1000 RWXA デバッグコントローラ
0x0000_3000 0x0000_1000 RWXA エラーデバイス
0x0000_4000 0x0000_1000 RWA ブートROM
0x0001_0000 0x0001_0000 RX ROM
0x0002_0000 0x0001_0000 RXC LBWIF ROM
0x0200_0000 0x0001_0000 RWA CLINT
0x0201_0000 0x0000_1000 RWA キャッシュコントローラ
0x0c00_0000 0x0400_0000 RWA 割り込みコントローラ
0x1000_0000 0x0000_1000 RWXCA LBWIF RAM
0x5400_0000 0x0000_1000 RWA シリアルデバイス
0x8000_0000 0x1000_0000 RWXCA メインメモリ

メモリ属性については、

  • R: Read
  • W: Write
  • A: Atomic Access
  • X: Execute
  • C: Cacheable

この中で分からないのはLBWIFというデバイスだ。検索してみるとLBWIFというのはLow Bandwidth Interfaceのことらしい。 Chipyardのドキュメントを見るとSerDesなどに使用するということらしいので、とりあえず実装に当たっては無視していいだろう。

RISC-V向けLinuxをビルドするための確認

RISC-V向けLinuxのビルドについて少し確認したいと思っていろいろと調査している。昔ビルドに使用していたのはfreedom-u-sdkのツールセットなのだが、今ははどうなっているのか。

github.com

それ以外の方法としても以下の方法が用意されていた。QEMUを使うのがデファクトスタンダードになっているようだ。

risc-v-getting-started-guide.readthedocs.io

とりあえず過去の方法としてfreedom-u-sdkを再ビルドしている。確認したいのは、Linuxのビルドにあたってどの命令が最低限必要なのか、ということ。 自作CPUの実装としてアトミック命令の実装が必要かなど、実装の優先度を確認するために使用したい。

$ git clone https://github.com/sifive/freedom-u-sdk.git
$ cd freedom-u-sdk
$ make -j$(nproc)