自作CPUのフロントエンドデコーダ部分が非常に重たいのだが,この改良を考えている.
重たい部分というのは,RVC命令(16-bit命令)を拡張して32-bit命令に変換する部分だ. RVC命令は32-bit命令(RVI命令)の中に混在して現れ,しかもRVCをサポートしている環境では32-bit命令を16-bitアラインに配置することが許可されているため非常に面倒になる.
例えば,128-bitの命令キャッシュブロックに以下のように命令が含まれていたとする.赤いブロック(16-bit)はRVC命令で,白いブロックはRVI命令を含む. 白いブロックはRVIなので,当然2ブロックで連続となる. この例では,とりあえずRVI命令のアラインがずれていることは考えず,最初の1ブロックにRVC命令が挿入されている一般的なケースを考える.
RVCかどうかを判定するのは16-bitブロックの下位2-bitが11かどうか(insn[1:0]==2'b11 ならば RVI, そうでなければRVC) を使って判定するので,RVCかどうかで切り出すブロックの位置が変わる. しかしこれを逐次的にやっているとクリティカルパスが伸びてしまうだろう.
そこで,とりあえずすべてのブロックをRVI命令に変換してみる.これで,16-bit x 8個の命令を(変換に失敗してもいいので) 32-bit x 8個の命令に変換する. この変換自体はおそらく重たいものではないだろう.各ブロック間の依存関係も存在しない.
そして,この後適切なブロックを切り出す処理が必要になる.イメージとしては以下のように切り出す.
で,それぞれを切り出す判定論理をどのように作るか考えてみる.
- insn0 : alignされているものとすると,
insn_origin[0]==RVC
ならばinsn_trans_rvi[0]
,そうでなければ{insn_origin[1], insn_origin[0]}
- insn1 : insn_rvc[0] だとすると,
insn_origin[1] == RVC
ならば insn_trans_rvi[1]そうでなければ
{insn_origin[2], insn_origin[1]}`
insn2
からがめんどい.insn2
が取り込まれる可能性があるのをリストアップして考える:
insn_rvc[1:0]==2'b00
:前の2つのブロックが両方ともRVCではないので,insn_origin[5:4]
が選ばれるinsn_rvc[1:0]==2'b01
:前の2つのブロックのうち1つがRVCなので,insn_origin[4:3]
が選ばれるinsn_rvc[1:0]==2'b10
:前の2つのブロックのうち1つがRVCなので,insn_origin[4:3]
が選ばれるinsn_rvc[1:0]==2'b11
:前の2つのブロックが両方ともRVCなので,insn_origin[3:2]
が選ばれる.
となる.つまり,当たり前の結果だが前に何個のブロックがRVCかということに依存することになる.
同様に,insn3
になると,
insn_rvc[2:0]==3'b000
:前の3つのブロックがすべてRVCではないので,insn_origin[7:6]
が選ばれるinsn_rvc[2:0]==3'b001
:前の3つのブロックのうち1つがRVCなので,insn_origin[6:5]
が選ばれるinsn_rvc[2:0]==3'b010
:前の3つのブロックのうち1つがRVCなので,insn_origin[6:5]
が選ばれるinsn_rvc[2:0]==3'b100
:前の3つのブロックのうち1つがRVCなので,insn_origin[6:5]
が選ばれるinsn_rvc[2:0]==3'b011
:前の3つのブロックが2つがRVCなので,insn_origin[5:4]
が選ばれる.insn_rvc[2:0]==3'b110
:前の3つのブロックが2つがRVCなので,insn_origin[5:4]
が選ばれる.insn_rvc[2:0]==3'b111
:前の3つのブロックがすべてRVCなので,insn_origin[4:3]
が選ばれる.
こう考えると,RVCの判定状況を Prefix Sum を使ってインデックスを計算する,ということになるか.