前回で例外情報をレジスタ1個に集約し、riscv-testsは52/54で止まっている。 残り2本は機能が未実装なので、いったん検証側の整備に回った回。 RTL全体の整形、store経路へのassertion追加、参照ISSを動かすための環境作りの3つ。
RTL全体を自作フォーマッタで整形し直す
Emacs側のSystemVerilogパッケージを入れ替えたので、整形規則もそれに合わせた。 パッケージにはCLIが付いているので、リポジトリ全体に一括でかけられる。
find core/rtl common/rtl -name '*.sv' -print0 \ | xargs -0 <sv-kit>/bin/sv-kit format --write
対象は64ファイル、差分は3932行の入れ替えになった。
zshは変数を単語分割しない
最初は素直に書いて失敗した。
SV=$(find core/rtl -name '*.sv') sv-kit format --write $SV # zsh ではファイル名 64 個が「1 つの引数」として渡る
bashなら単語分割されるが、zshはしない。
フォーマッタ側は「そんな名前のファイルは読めない」と言い、こちらは出力の末尾だけ見て成功したと勘違いしていた。
find -print0 | xargs -0に変えて解決した。
意味が変わっていないことをどう確認するか
整形は空白しか変えないはずだが、3932行の差分を目で追うのは現実的ではない。 機械的に確認できる。
for f in $(git diff --name-only); do a=$(git show HEAD:$f | tr -d '[:space:]' | md5sum) b=$(tr -d '[:space:]' < $f | md5sum) [ "$a" != "$b" ] && echo "CONTENT DIFF: $f" done
空白を全部落としたうえでハッシュを取り、整形前後で一致するかを見る。 64ファイルすべてで一致したので、トークンの削除や並べ替えは起きていない。 そのうえでunit TB 15本とriscv-testsを流し、数値が完全に同じであることも確認した。
「安全な機械的変更」だと分かっていても、確認手段を持っているかどうかで作業の気楽さが違う。
黙って壊れる経路にassertionを置く
前回までに、storeの経路で2件の問題を直していた。
- 分岐予測ミスのflushがLSUに届かず、予測ミスの経路のstoreがSTQに残る
- STQがFIFOの先頭を無条件に書き出すので、残ったentryがあると以降のstoreが1つずつずれる
1は配線の修正で済んだが、2は構造の問題が残っていた。
STQはi_cmt_rob_idを受け取っているのに、照合に使っていなかった。
rob_idで照合する
STQのentryにrob_idを持たせ、commitのrob_idと一致したときだけdequeueするようにした。 そのうえでassertionを置く。
assert property (@(posedge i_clk) disable iff (!i_rst_n) (i_cmt_valid && !i_flush_all) |-> (r_stq[r_tail[STQ_W-1: 0]].valid && (r_stq[r_tail[STQ_W-1: 0]].rob_id == i_cmt_rob_id))) else $error("scariv_stq: commit rob_id %0d does not match tail entry", i_cmt_rob_id);
照合を足しただけだと、ずれたときは「dequeueしない」だけで、ROB側はcommitしたつもりで進む。 症状は数百cycle後に別の場所で出る。 assertionがあれば、ずれたその瞬間に止まる。
1 cycleに2つstoreがretireする場合
ROBのo_cmt_storeは1本で、同じcycleに2つのstoreがretireすると1つしかdequeueされない。
対処は2通りあった。
| 案 | 内容 |
|---|---|
| A | commitを複数同時に出せるようにし、STQも1 cycleに複数dequeueする |
| B | retireの連鎖をstoreのところで切り、1 cycleに1つに制限する |
Bにした。 DTCMのwrite portが1本なので、Aにしても結局1 cycleに1つしか書けない。 memory側のportが増える段階まで、この制約を持っておく方が構造が素直になる。
実装は、分岐や例外でretireの連鎖を切っているのと同じ形で条件を1つ足すだけ。 そのうえで前提をassertionに書く。
assert property (@(posedge i_clk) disable iff (!i_rst_n) $countones(w_assert_ret_store) <= 1) else $error("scariv_rob: %0d stores retired in one cycle", $countones(w_assert_ret_store));
今の条件は前のlaneとしか比較していないので、dispatch幅が2の今だけ厳密になる。 幅を広げたときにlane 0とlane 2がstoreだと通ってしまうが、そのときはこのassertionが検出する。 「今は正しいが前提に依存している」ことを、コメントではなく実行時に効く形で残せる。
参照ISSを動かすための環境作り
次の関門は参照ISSとの命令単位の突合わせで、完了基準は「100万命令でmismatch 0」。 まずISSを動かすところから始めた。
入手先が変わっている
設計ドキュメントにはWhisperのリポジトリをchipsalliance/riscv-whisperと書いていたが、これは404だった。
正しくはchipsalliance/VeeR-ISSで、Western DigitalのSweRV-ISSがVeeRに改称された流れをそのまま引きずっている。
ドキュメントも直した。
ホストに入れられない
Whisperはboost_program_optionsを要求するが、ホストにboostの開発パッケージが入っていない。 sudoが要るので、ここで止まる。
合成側はすでにDockerに逃がしてある (Yosys / OpenSTA / SKY130を焼き込んだイメージがある) ので、検証側も同じ形にした。
sim/dockerに、Verilator、Whisper、riscv64-unknown-elf gcc、elf2hexを入れたイメージを作る。
ELFのビルドからhex変換、54本の実行までコンテナ内で完結する。
ビルドで踏んだもの
素直には通らなかった。順に記録しておく。
Ubuntu 24.04 (GCC 13) ではWhisperがコンパイルできない。 libstdc++のヘッダ内で「instantiation後の部分特殊化」というエラーが出る。 Whisperの更新は2023年で止まっているので、baseを22.04 (GCC 12) にした。ホストのコンパイラとも一致する。
BOOST_DIR=/usrを渡すとビルドが壊れる。 makefileが-isystem $(BOOST_DIR)/includeを付けるため、-isystem /usr/includeとなり、libstdc++が使う#include_next <stdlib.h>の探索順が崩れる。 distroのboostは既定の探索パスにあるので、BOOST_DIRは空のままでよい。elf2hexに2系統ある。 テストのMakefileが呼んでいるのは位置引数の形式。
elf2hex <width> <depth> <elf> [base]
一方、sifive/elf2hexは--bit-width / --inputの形式で、base addressも渡せない。
位置引数の方はfesvrに入っているものだった。
Spikeも同じelf2hexを持っているので一度そちらで通そうとしたが、configureがboost_systemのリンクに失敗する。 Ubuntu 22.04のboost_systemはほぼheader-onlyで中身が空なので、リンクテストが通らない。 今回はfesvr単独をビルドした。副の参照ISSとしてSpikeを入れるのは、必要になった時点で改めて考える。
fesvrは2019年で更新が止まっており、
dtm.ccに#include <stdexcept>が無い。 昔は別のヘッダ経由で入っていたものが、新しいlibstdc++では入らない。 Dockerfile側で1行足してビルドした。ホストで作った
obj_dir_*が残っていると、コンテナ内のビルドが失敗する。 Verilatorの生成物に絶対パスが入るため。切り替えるときは消す。
結果
コンテナ内で、unit TB 15本の数値がホストと完全に一致し、riscv-testsも52/54で同じになった。
sim/docker/build.sh # 初回のみ 15 分程度 sim/docker/run.sh make test-rob sim/docker/run.sh make riscv-tests sim/docker/run.sh whisper --help
依存を1つ増やす形ではあるが、boostのような「ホストに入れるには権限が要るもの」が出てきた時点で、環境ごと固める方が結局早い。 バージョンを固定してあるので、他のマシンやCIにもそのまま持って行ける。