【RISC-V/Chisel】パイプラインのストールで躓いたところ
はじめに
「RISC-VとChiselで学ぶ はじめてのCPU自作」をやっていて少し躓いたところがあるのでまとめておきます。
自分は今までソフトウェア中心にやってきたので回路設計の考え方に慣れないところがあります、、、
概要
該当の箇所は、パイプラインを実装する際に、「EXステージでjmp命令を検出したらIF、IDステージをストールする」という処理です。
レジスタとワイヤ
まず混乱の最大の原因はChiselで使われる2つの変数(CPUで用いられる2つの要素)についての理解が曖昧だったことですね。
まずはここを整理しておきます。
レジスタ(RegInit):
順序論理回路
クロックが進むと値が更新される
ワイヤ(Wire):
組み合わせ論理回路
入力が即座に反映される
また、
val pc_plu4 = reg_pc + 4.U(32.W)
のように、RegInitでもWireでもない変数も定義可能ですが、これは回路図では、配線の途中の信号状態に名前をつけたもの、と解釈しています。
詳細
で、実際僕が躓いたのは以下の部分です。
この本の設計では、IFステージとIDステージの無効化のために以下のような処理を入れています。
val id_reg_inst = RegInit(0.U(32.W))
val exe_jmp_flg = Wire(Bool())
//・・・
//IF statge
//・・・
id_reg_inst:= MuxCase(if_inst, Seq(
exe_jmp_flg -> BUBBLE,
)
)
//ID statge
val id_inst = Mux(exe_jmp_flg, BUBBLE, id_reg_inst)
id_reg_inst:IDステージにおいて、指令を格納するレジスタ
if_inst:IFステージでフェッチしてきた指令値
exe_jmp_flag:指令がjmpかどうかを表すフラグ。EXステージで確定。
1つ目の式でIFステージを無効化し、2つ目でIDステージを無効化しています。
疑問1:2つ目の式いる?
id_reg_instはレジスタだから、次のサイクルまで値は変わりません。
そのため、2つ目の式でid_instにBUBBLEが反映されるのはjmpが確定した時(サイクルXとする)の次のサイクルからになります。
その場合、サイクルXで処理した結果はEXステージに流れてしまいます。
疑問2:なぜサイクルXの結果も無効化できる?
上記のように考えた時、次に出てきた疑問がこれです。
それは2つ目の式を入れても同じでは?id_instをBUBBLEにできるのはexe_jmp_flagの値が確定した次のサイクルからでは?
これに対する答えは「exe_jmp_flagはワイヤだから」ですね。
レジスタとは違いワイヤは値が確定した途端反映されるので、サイクルXにおいてid_instを書き換えることができます。
上記の勘違いが発生したもう一つの原因は、「書いたコードが上から下に、1度だけ実行される」と勘違いしていたことですね。
今はプログラミング言語でコードを書いてはいますが、実際は回路の設計をしています。
回路の各部品では入力信号が常に評価されています。
Wireは文字通り回路の部品間が結線されていると考えます。
exe_jmp_flagの値が変われば、Mux()の出力も変わります。
最後に
特に今までソフトウェア中心にやってきた人間にとっては、「実際には回路を設計している」という意識が必要ですね。
慣れないうちは頭の中に回路図を思い浮かべながらコーディングしていくのがよさそうです。
最新記事
すべて表示現象 配列がある インデックスを表す変数が配列のサイズ内かをチェックし、サイズ内の場合のみ要素にアクセス というよくあることをやろうとした val array = Seq.fill(ARRAY_SIZE)(...) when(i.U < ARRAY_SIZE){...
概要 Queueを使ってデータのやり取りをする場合、以下のような操作が可能です。 Queue.io.enq.valid:falseにするとデータを入れない Queue.io.deq.valid:falseにするとデータを取り出さない これらの使い分けについてまとめてみます...
やりたいこと 類似したユニットが複数ある これらの処理は大部分が共通で一部のみ異なる 条件に応じて適切なユニットを1つ選び、処理を実行させる やろうとしたこと これを実現するために 親クラスを定義し、共通処理はここに記述...
Opmerkingen