|
13 |
RISC-Vプロセッサの設計 (38) |
命令メモリと逆アセンブル(Step 2)
Step 2では、Step 1で仮に置いていた命令メモリを、BRAMで作った本物の命令メモリに置き換えました。あわせて逆アセンブル関数を作り、<ID>で命令の中身が見えるようにしました。
命令メモリ
命令メモリ(IMem)は、BRAMCoreの同期BRAMで作りました。容量は64KBです。同期BRAMなので、アドレスを渡した次のサイクルに命令が出てきます。これを<PC>と<IF>の2段に割り振りました。
- <PC>:次のPCを決めたら、同じサイクルにそのPCでIMemにreqを出します。
- <IF>:次のサイクルに、IMemの出力から命令を受け取ります。
// <IF> 前のサイクルに<PC>がreqした命令を受け取る
rule if_stage (!ifKill && !stallW.if_);
let x = pc2if.first; pc2if.deq;
let inst = imem.data;
if2id.enq(IF2ID { pc: x.pc, epoch: x.epoch, sn: x.sn, inst: inst });
endrule
// <PC> PCの箱(last) → +4 → Mux(redirectが優先) → PCの箱、同じPCでreq
rule pc_stage (!stallW.pc);
let cur = pc2if.last;
Addr pc = cur.pc + 4;
Bool epoch = cur.epoch;
if (redirect[1] matches tagged Valid .target) begin
pc = target;
epoch = !cur.epoch;
redirect[1] <= tagged Invalid;
end
pc2if.enq(PC2IF { pc: pc, epoch: epoch, sn: cur.sn + 1 });
imem.req(pc);
endrule
<IF>が止まったときに、命令が消えないかが気になるところです。BRAMの出力は、次にreqが来るまで同じ値を出し続けます。<IF>が止まっている間はPCの箱が満杯なので、<PC>はenqできず、reqも出しません。そのため、<IF>が再び動いたときにも、BRAMには自分の命令が残っています。この性質のおかげで、出力を受けるレジスタやFIFOを足さずに済みました。Step 1のランダムに段を止めるテストを、そのまま全部通ることも確かめました。
逆アセンブル
逆アセンブル関数は、pcと命令コードを受け取って表示用の文字列(Fmt)を返す、デバッグ専用の関数です。回路には入りません。書き方は、objdumpの-M no-aliases,numericに合わせました。mvやretのような別名を使わず、レジスタはx0〜x31で書きます。
| 命令コード | 逆アセンブル |
|---|---|
| 7f9010ef | jal x1,80002000 |
| 0047a703 | lw x14,4(x15) |
| fec796e3 | bne x15,x12,8000201c |
| 00008067 | jalr x0,0(x1) |
RV32IとZicsr、fence.i、ecall/ebreak/mret/wfiに対応しています。
Konataの左の欄に出す
<ID>は逆アセンブルの結果を、Kanataログの左の欄に書き足します。Step 1では左の欄はPCだけでしたが、Step 2からは「PC ニーモニック」になり、Konataでどの命令が流れているかが一目で分かります。<ID>まで行かずに捨てられた命令は、PCだけが出ます。
| 命令 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
| 80000000 auipc x2,0x10 | PC | IF | ID | EX | MA | WB | |||
| 80000004 addi x2,x2,0 | PC | IF | ID | EX | MA | WB | |||
| 80000008 jal x1,80002000 | PC | IF | ID | EX | MA | WB | |||
| 8000000c | PC | IF | ID | ||||||
| 80000010 | PC | IF | |||||||
| 80002000 lui x15,0x80002 | PC | IF | ID | EX | |||||
| 80002004 addi x14,x15,104 | PC | IF | ID |
どう確かめたか
期待値はobjdumpです。riscv-tests(rv32ui-p)40本とfiboに出てくる全命令に、CSR命令や即値の端の値などを集めた命令列を加えて、合計9431命令をobjdumpで逆アセンブルしました。同じ命令を逆アセンブル関数に通し、1行ずつ比べて、bsimとVerilatorの両方で全部一致しました。
さらに、fiboをパイプラインに流しました。<ID>がKanataの左の欄に書いた29命令が、objdumpの結果と一致しました。

図1149.1 パイプラインビューア
できたこと
命令は本物のメモリから読まれるようになり、Konataで「どの命令が、どの段に、いつ居るか」が読めるようになりました。次のStep 3では、デコーダ、レジスタファイル、ALUを入れ、命令が実際に計算をするようにします。
Leave a Comment