our_cpu (1)
riscv32iを実装して、ある程度テストを通すまで
アーキテクチャ
riscv32iに従う(現状)
参考
とりあえずaddをクリアする
ひとまずcore.svにベタ書きして進めていく
addを認識できる状態
ブロッキング代入と、ノンブロッキング代入の使い分けがわからなくて詰まってた
sub, addi
やった
write backを適当にベタ書きしているけど、これはwrite backステージとして分けるため、少し変える必要がありそう
次はファイル(モジュール)分割してみる
モジュール分割
aluを書いているとき、def.svでerror: Typedef identifier "instinfo_t" is already a type name.というエラーが発生した
名前衝突していそうな感じなので、参考リポジトリそのままinfndefなどを付けてみたら行けた
decodeにあるalwaysと、coreにあるalwaysが実行される順番がばらばらで、プログラムカウンタに従って動かない
いったんコミット
正直、直し方が分からない
よさげな資料があったので、これを真似しながらsystem verilogやっていくほうがよさそう
learn-fpgaに従って、もう一度!!!
step4まで
よさそう
ifdef BENCHを使いつつ、iverilog -DBENCHみたいにマクロ変数BENCH付きで呼び出すことで、シミュレーション上だけの実装を表現している
syscallで止まるところと、デバッグ出力の部分
なるほど、と思った
step5,6,7,8,9,10まで
write backが思っていた所(EXECUTEの後ろ)になくて?となった
step7でアセンブラを導入したが、これすごい
MEMに対応するビット列を代入するというアイデアはなかった
step11
いくつかファイルを変更した
topでclkの生成をしていたが、これをtestbench_iverilog.svに移行
learn-fpgaにあるSOC moduleに当たる部分をtop.svに変更
learn-fpgaにあるProcessorにあたる部分はcore.sv
メモリは独立させてmemory.svに
mem_rstrbとはいったい???????
コメントでgoes high when processor wants to readとあるので、read_enableかなと思う
流石にrstrbじゃわからないので、mem_r_enableにしておく
step12
賢いやり方で実装を簡略化することが目的の章
だけど、個人的には可読性が下がるのが嫌なので、この章はスキップすることにする
また必要になったらやるかも
step16まで
swだけ実装した
sh, sbはまだ実装していない
フィボナッチ数列のコードを実行できるようになった!!
ここからどうする?
やること
sh, sb実装する
uartに対応する
riscv-testsを通す
実機ないし、テストを通すことを優先することにする
とりまcsr, ecall を実装した
iverilog でparameterが未対応だったので、csrレジスタのアドレスがベタ書きの状態になっている
riscv-tests通す
riscv-testsをビルドする
64bit版のコンパイラが必要なので、頑張る必要があった
その後は、ChiselのCPU本の20章に従って進めていく
elf -> bin -> hex
本の通りにやる
リンカスクリプトを書き換えて、0x0から命令が始まるようにする
テストの終了条件
これも本と同様で、<write_tohost>のところで無限ループになって終了する
ただ、(本ではpc=44で終了だが、)以下のように少し変わっていて、pc=4cが終了条件となる
code:riscv
0000003c <write_tohost>:
3c: 00001f17 auipc t5,0x1
40: fc3f2223 sw gp,-60(t5) # 1000 <tohost>
44: 00001f17 auipc t5,0x1
48: fc0f2023 sw zero,-64(t5) # 1004 <tohost+0x4>
4c: ff1ff06f j 3c <write_tohost>
それ以外はたぶん同じで、gp=1でテストがPASS、gp=2以上のときはその番号のテストが落ちていることになる
テストを走らせてみたらgp=0x539 !?
rv32ui-p-addを走らせたら、gp=0x539で終了した。どゆこと???
本来、落ちているテスト番号がgpに入るが、このテストケースのテスト番号は38まで
謎である
実行のログを見た感じ、<pass>にあるecallを通っているように見える(pc=0x698)
実行ログでpc,gpを見えるように変更すると、テストをパスして<write_tohost>に入るとき(pc=0x38からpc=0x3cの間)にgpの値がおかしくなることが分かった
pc=38のところでori gp, gp, 1337とあるので、ここで書き換わっていることがわかった
1337は16進にすると539
上手くいってない理由はわかった
code:riscv
00000004 <trap_vector>:
4: 34202f73 csrr t5,mcause
8: 00800f93 li t6,8
c: 03ff0863 beq t5,t6,3c <write_tohost>
10: 00900f93 li t6,9
14: 03ff0463 beq t5,t6,3c <write_tohost>
18: 00b00f93 li t6,11
1c: 03ff0063 beq t5,t6,3c <write_tohost>
~~~~~~~~~~~~~~~~~~~~
テストが落ちるor通ると、<trap_vector>に飛ぶようになっているが、ログを見た感じだとpc=8に飛んでいる(本当は上の通りpc=4であってほしい)
pc=4のcsrrが実行されないと、t5にmcause(=11)が入らず、その後ろのbeqで実行のゴールにたどり付けないことが分かった
pc=4に正しく飛んでくれればよさそうなので、ecallで正しく飛べているか(mtvec=4になるか)を調べるとよさそう
mtvecにはt0の値が渡されているので追跡したが、auipcの実装が違う(12ビットシフトしていない気がする)ので直す
あっていた
pc=13cの時にmtvec=8になって、ずっとそのままなのでここが怪しい
悪い所見つけたかもしれない
code:riscv
130: 00000297 auipc t0,0x0 <- ログでは t0 = 304(正しい)
134: ed428293 addi t0,t0,-300 # 4 <trap_vector> <- ログでは t0 = 308(怪しい)
138: 30529073 csrw mtvec,t0 <- ログでは t0 = 8(正しい)
t0=304の状態でauipcを通過するとt0=308になってしまい、その後にaddiで-300される
そのため、最終的にmtvec=8になってしまっている
t0=308になってしまう理由を追跡すればよさそう
前の部分でauipcの実装が違いそうなことがわかっていたので、直す
これはあっていた
本当に悪さしていたのは、pcの更新場所だった
pcをEXECUTEステージで更新していたが、mtvecを決定するのはWBステージだったので、要求値より+4された状態になっていた
pcの更新をWBにして対応した
これで、<trap_vector>に飛んだ時にpc=4になるようになった、が...
まだgp=0x539のままだった
pc=4のcsrrでt5=11になってほしいはずだが、なっていないのが原因かな
mcause=11だったので、これは大丈夫そう
is_csrの判定がまちがっていたので修正
csr命令はWBする必要があるが、wb_enableの条件にis_csrが含まれていなかった
入れてみると、無限ループしてしまった(pc=xxxxxxxみたいになった)
ログを見ると、cc: f1402573 csrr a0,mhartidで止まっていて、未実装のmhartidを踏むことでpcが未定義値になったのだろう
とりあえず、pcが不定値になったら止まるように変更した
ここでいったんコミットする
どうする??
csr命令に必要なレジスタを実装していないことが原因
選択肢としては、1.csr実装する、2.無視してできそうなテストだけ頑張る
1.csr実装する
ざっと見た感じ、mhartid, mstatus, medeleg, stvec, mtvec, pmpaddr0, pmpcfg0, midelegが必要そう
2.無視する
<trap_vector>入ったタイミングを終了ポイントにするという手もある
csr_regsを初期化すればある程度解決するのでは??とも思った
2.無視する方向でいく
csrを0で初期化した
ecallのときに、csr_regsに値を反映できていなかったので、MEMステージの条件分岐にis_ecallを追加
mcauseの定義(どのモードからのecallか)を勘違いしていた
mcauseの正しい値は10進数だったが、勘違いand間違えて16進数で書いてしまっていた
これを直したら、ちゃんとgp=1で終了し、正しく動かせた!!!!
いよいよテスト通す!
rv32ui-p-***をやっていく
add~jalrまではpass
fence_iはfail
lb, lbu, lh, lhu, lui, lw がfail
or, ori はpass
sb, sh はfail
simpleはpass(このテストのアセンブリみたけど、trap_vectorに飛ぶテストぽい??)
sll~sltuはpass
sra, sraiはfail
srl, srli, subはpass
swはfail
xor, xoriはpass
テストを一括で!
落ちているテストを通すのは後にして、一括でテストを走らせるようにする
今は、コメントアウトしてファイルを選択している
今回はスクリプトを作って、一括実行を行えるようにする
verilog的にはファイル名(とパス)を実行時引数として受け取れればよい
$value$plusargsというマクロがあり、これでファイル名を取得し、$readmemhに渡す形にした
イイ感じにできたが、romに命令をロードすると、WARNING ~~ Not enough words in the file for the requested rangeが出てしまう
romが長過ぎるのが原因だが、動的に長さを変えるのは難しそうなので、warningを消す方向性でいく
iverilogのオプションで、warningを消せないか? -> できなそう
verilog側でPASSFAILを出力して、grepする作戦で実装した
バグ直してくよ〜
lwするとき、write backできてなかったので修正
しかしまだlwのテストは落ちる
swがメモリの正しい場所に書き込めていない
書き込みアドレスをミスっていた(そろそろメモリ配列を32bit->8bitにしないと)
swのテストはPASS
sra, sraiが落ちている
sraをした結果の値が変な値になっていた
原因は↓だった
三項演算の片側がsign、片側がunsignのとき、全体はunsignとして解釈される。>>>はsignの時のみ算術シフトになるため、修正前は論理シフトになっていたため正しい動作をしなかった
これでだいたいPASSした
落ちたのは...
未実装:fence, sb, sh
ロード:lb, lbu, lh, lhu, lw, ma_data
未実装は置いておくとして、ロード系はメモリにテスト用の値が入らないという問題があり、これを解決できなかった
lwの動作自体は大丈夫そうな気がするので、これは一旦保留にしようかなと
次はFPGAに実装してみる