Chiselを使ったRISC-Vの勉強(2)
汎用レジスタ(x0-x31)の実装
RISC-V(RV32I)の汎用レジスタは32bit長で32個あります。
https://content.riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf
まずRISC-Vの命令デコードには必須なのでこちらを用意します。
Chiselだとレジスタ宣言、ゼロ初期化まで1行でかけます。x0 = zero registerで固定ですが、めんどくさいのでx1-x31とまとめて確保しました。
val rv32i_reg = RegInit(VecInit(Seq.fill(32)(0.U(32.W)))) // x0 - x31:All zero initialized
デバッグトレース
今のところ、デバッグトレースで必要なものは何かを考え、テストオブジェクトに入出力できるようにしました。
- PC
- Program counterの読み書きができるようにしました。halt信号(後述)をtrueにしてから書き換え推奨。
- 命令メモリ
- 前回の記事で既に実装済み。
(ID)命令デコードの実装
力弱く、addiだけ実装しました。これを実装すると、上記で用意したx0-x31までのテストができるからです。
以下のようなアセンブラを記述します。
addi x1, x0, 0; // x1 = x0 + 0 (=0) addi x2, x0, 1; // x2 = x0 + 1 (=1) addi x3, x0, 2; // x3 = x0 + 2 (=2) addi x4, x0, 3; // x4 = x0 + 3 (=3) …. addi x31, x0, 1E; x31 = x0 + 0x1E (=30)
ここまで説明しておいてアレですけどアセンブラ流し込むスクリプトを記述してなかったので(おい)、、機械語で書きましょう。
AddiはRISC-Vの中でI-Typeの命令語で12bitの即値(imm)、1つの代入元(ソース)レジスタ(rs1)、1つの代入先(デスティネーション)レジスタ(rd)から構成されます。rs1はx0~x31、rdはx1~x31上にあるいずれかのレジスタ*1となります。
b????_????_????_????_?000_????_?001_0011
であれば良いことがわかります。16進数で表記すると、
0x00000093, // addi x1,x0,0 (x1 = x0 + 0 = 0) 0x00100113, // addi x2,x0,1 (x2 = x0 + 1 = 1) 0x00200193, // addi x3,x0,2 (x3 = x0 + 2 = 2) 0x00300213, // addi x4,x0,3 (x4 = x0 + 3 = 3) .... 0x01E00F93 // addi x31,x0,30
となります。うっひょ、マシン語とか超楽しいマジ無理。早くアセンブラからバイナリ流し込む機能実装しないと。
Chiselで機械語の判定はBitPatを使うと簡単にできます。
when (inst_code === BitPat("b????_????_????_????_?000_????_?001_0011")){ // ADDI = imm12=[31:20], src=[19:15], funct3=[14:12], rd=[11:7], opcode=[6:0] val dest = inst_code(11,7) // rd-pointer val imm = inst_code(31,20) // imm val src = inst_code(19,15) // src-pointer val dsrc = rv32i_reg(src) //rs when (0.U < dest && dest < 32.U){ //アドレスは1~31か? rv32i_reg(dest) := dsrc + imm } }
と言った感じです。
テストコードを書き直します。1部抜粋です。
poke(c.io.sw.halt, true.B) // CPUを停止 step(1) for (addr <- 0 to (memarray.length * 4 - 1) by 4){ poke(c.io.sw.wAddr, addr) poke(c.io.sw.wData, memarray(addr/4)) // 指定アドレスに機械語をロード println(f"write: addr = 0x${addr}%08X, data = 0x${memarray(addr/4)}%08X") step(1) } poke(c.io.sw.w_pc, 0) // プログラムカウンタをゼロに戻す=reset step(1) // fetch pc poke(c.io.sw.halt, false.B) // CPUを再開させる step(1) for (lp <- 0 to (memarray.length - 1) by 1){ val a = peek(c.io.sw.addr) val d = peek(c.io.sw.data) println(f"read : addr = 0x$a%08X, data = 0x$d%08X") // メモリ上の機械語のダンプ step(1) } step(1) poke(c.io.sw.halt, true.B) step(1) for (lp <- 0 to 31 by 1){ poke(c.io.sw.gAddr, lp) step(1) val d = peek(c.io.sw.gData) println(f"read : x$lp%2d = 0x$d%08X") //x0 ~ x31レジスタの内容をダンプ step(1) }
動作確認
コード全文
コードは今後も漸進的に更新されるので、以下のように-bでタグを指定してcloneするのがいいと思います。
git clone http://github.com/panda5mt/KyogenRV -b 0.0.4 --depth 1
きょうはここまで。