RISC-V対応のパイプラインプロセッサをちまちま作ってみている。基本的な命令発行制御などはできるようになってきたが、プロセッサとしてベンチマークなどを動作させるために必要になってくるのは、やはりシステムレジスタの実装だ。
RISC-Vのシステムレジスタは、Privileged Architecture Manualに纏まっており、権限ごとにアクセスできるシステムレジスタが固まっている。 また、命令からこれらのシステムレジスタにアクセスするためには、システムレジスタごとに割り当てられているアドレスを使って読み書きを行う。
これらをアウトオブオーダのプロセッサに組み込む場合にはどうしたらいいのだろう? 汎用プロセッサの場合にはリネームマップを使って32本のアーキテクチャレジスタから物理レジスタへのマッピングをするが、これに追加してシステムレジスタ用のリネームマップを作るか? それはとても無駄に思える。それは、システムレジスタにアクセスするのはかなり稀であり、そのために巨大なリネームマップを用意するのはコストに見合わない。
したがって、システムレジスタのアクセスは通常のパイプラインプロセッサによるアクセスと同様、IDは振らずに通常のアクセスとして扱うか、さらに扱いを下げて、フォワーディングもしない、つまりシステムレジスタの実体に最新の値が書き込まれるまでは、後続のシステムレジスタにアクセスする命令はストップする、というフォワーディングなしのパイプラインと同様な動きをさせる。
これにより、万が一システムレジスタへアクセスが頻発するプログラムの場合は性能は激落ちとなるが、そんなプログラムは滅多にないと仮定して、このアイデアを採用することにする。
CSRレジスタのVerilog実装を、ISSから自動生成する
ところで、CSRレジスタ(RISC-Vのシステムレジスタ)は非常に多く、それぞれで書き込みができる権限、書き込みできるフィールドも異なる。 これらについてひとつづつマニュアルで実装していたら日が暮れるので、ISSが持っているRubyのテーブルからCSRのアクセスクラスを自動的に生成する機構をVerilog-HDLに発展させる。
自作ISSには、RISC-VのアーキテクチャテーブルからC++の実装を自動的に生成する機能を持っている。
## start of RISC-V system registers # ['Number', 'Privilege', 'Name'] # User Level CSR $sysreg_table[ 0] = Array[0x001, 'URW', 'fflags' , Array[Array[31, 0, 'fflags' , 'RW']]] $sysreg_table[ 1] = Array[0x002, 'URW', 'frm' , Array[Array[31, 0, 'frm' , 'RW']]] # temporary implementation $sysreg_table[ 2] = Array[0x003, 'URW', 'fcsr' , Array[Array[ 7, 5, 'frm' , 'RW'], Array[ 4, 0, 'fflags' , 'RW']]] # temporary implementation $sysreg_table[ 3] = Array[0xC00, 'URO', 'cycle' , Array[Array[31, 0, 'cycle' , 'RW']]] # temporary implementation $sysreg_table[ 4] = Array[0xC01, 'URO', 'time' , Array[Array[31, 0, 'time' , 'RW']]] # temporary implementation $sysreg_table[ 5] = Array[0xC02, 'URO', 'instret' , Array[Array[31, 0, 'instret' , 'RW']]] # temporary implementation $sysreg_table[ 6] = Array[0xC80, 'URO', 'cycleh' , Array[Array[31, 0, 'cycleh' , 'RW']]] # temporary implementation $sysreg_table[ 7] = Array[0xC81, 'URO', 'timeh' , Array[Array[31, 0, 'timeh' , 'RW']]] # temporary implementation
これを発展させて、このテーブルからシステムレジスタのVerilog実装を自動的に生成するスクリプトをRubyで記述した。
まずは、それぞれのCSRエントリに対応するユニットを生成する。
def gen_csr_module(fp, csr_info) fp.printf("module CSR_%s (\n", csr_info[SYSREG::NAME]) fp.printf(" input wire CPU_CLK,\n") fp.printf(" input wire CPU_RESET,\n") fp.printf(" input wire CSR_WE,\n") fp.printf(" input wire [31: 0] DATA_IN,\n") fp.printf(" output wire [31: 0] DATA_OUT);\n\n") csr_info[SYSREG::BITFIELD].each {|bitfield| bit_max = bitfield[SYSREG_BITFIELD::MAX] bit_min = bitfield[SYSREG_BITFIELD::MIN] bit_name = bitfield[SYSREG_BITFIELD::NAME] bit_rw = bitfield[SYSREG_BITFIELD::RW] if bit_rw != 'R' then fp.printf(" reg [%d:%d] csr_bit_%s;\n", bit_max, bit_min, bit_name) end }
内部の実装なのだが、RW可能なビットが存在すればレジスタを定義し、そうでなければ定数のみ出力するモジュールとする。
bit_rw = bitfield[SYSREG_BITFIELD::RW] bit_max = bitfield[SYSREG_BITFIELD::MAX] bit_min = bitfield[SYSREG_BITFIELD::MIN] bit_name = bitfield[SYSREG_BITFIELD::NAME] if bit_rw != 'RW' then fp.printf(" %d'h0,\n", bit_max - bit_min + 1) else fp.printf(" always @ (posedge CPU_CLK) begin\n") fp.printf(" if (CPU_RESET) begin\n") fp.printf(" csr_bit_%s <= %d'h0\n", bit_name, bit_max - bit_min + 1) fp.printf(" end else begin\n") fp.printf(" csr_bit_%s <= CSR_WE ? DATA_IN[%d:%d] : csr_bit_%s\n", bit_name, bit_max, bit_min, bit_name) fp.printf(" end\n") end }
最後に、それぞれのビットフィールドを結合してシステムレジスタの値として出力する。
fp.printf(" assign DATA_OUT = {\n") csr_info[SYSREG::BITFIELD].each_with_index {|bitfield, index| bit_rw = bitfield[SYSREG_BITFIELD::RW] bit_max = bitfield[SYSREG_BITFIELD::MAX] bit_min = bitfield[SYSREG_BITFIELD::MIN] bit_name = bitfield[SYSREG_BITFIELD::NAME] fp.printf(" csr_bit_%s", bit_name) if index == csr_info[SYSREG::BITFIELD].length-1 then fp.printf("\n") else fp.printf(",\n") end }
これにより、以下のようなCSR用モジュールを含むVerilogファイルが生成された。
module CSR_fflags ( input wire CPU_CLK, input wire CPU_RESET, input wire CSR_WE, input wire [31: 0] DATA_IN, output wire [31: 0] DATA_OUT); reg [31:0] csr_bit_fflags; always @ (posedge CPU_CLK) begin if (CPU_RESET) begin csr_bit_fflags <= 32'h0 end else begin csr_bit_fflags <= CSR_WE ? DATA_IN[31:0] : csr_bit_fflags end assign DATA_OUT = { csr_bit_fflags }; endmodule module CSR_frm ( input wire CPU_CLK, input wire CPU_RESET, input wire CSR_WE, input wire [31: 0] DATA_IN, output wire [31: 0] DATA_OUT); reg [31:0] csr_bit_frm; ...
さらに、これらのモジュールをまとめ上げる上位階層のVerilogファイルを生成する。
まずはすべてのCSRモジュールを定義し、アドレスデコード、マッチしたCSRに対して読み書きを実行する。
$sysreg_table.each {|sysreg_info| csr_decode_fp.printf(" wire csr_match_%-10s = (CSR_ADDR == 12'h%03x);\n", sysreg_info[SYSREG::NAME], sysreg_info[SYSREG::ADDR]) csr_decode_fp.printf(" wire csr_we_%-13s = CSR_WE & csr_match_%s;\n", sysreg_info[SYSREG::NAME], sysreg_info[SYSREG::NAME]) } $sysreg_table.each {|sysreg_info| csr_decode_fp.printf(" CSR_%-10s u_csr_%-10s (.CPU_CLK (CPU_CLK), .CPU_RESET (CPU_RESET), .CSR_WE (csr_we_%-10s), .DATA_IN (CSR_DATA_IN), .DATA_OUT (csr_%s_data));\n", sysreg_info[SYSREG::NAME], sysreg_info[SYSREG::NAME], sysreg_info[SYSREG::NAME], sysreg_info[SYSREG::NAME]); }
これにより、以下のようなVerilogファイルが自動的に生成される。
module csr_main ( input wire CPU_CLK, input wire CPU_RESET, input wire CSR_WE, input wire [11: 0] CSR_ADDR, input wire [31: 0] CSR_DATA_IN, output wire [31: 0] CSR_DATA_OUT); wire [31: 0] csr_fflags_data; wire [31: 0] csr_frm_data; wire [31: 0] csr_fcsr_data; wire [31: 0] csr_cycle_data; ... wire [31: 0] csr_uarch13_data; wire [31: 0] csr_uarch14_data; wire [31: 0] csr_uarch15_data; wire csr_match_fflags = (CSR_ADDR == 12'h001); wire csr_we_fflags = CSR_WE & csr_match_fflags; wire csr_match_frm = (CSR_ADDR == 12'h002); wire csr_we_frm = CSR_WE & csr_match_frm; wire csr_match_fcsr = (CSR_ADDR == 12'h003); wire csr_we_fcsr = CSR_WE & csr_match_fcsr; wire csr_match_cycle = (CSR_ADDR == 12'hc00); wire csr_we_cycle = CSR_WE & csr_match_cycle; ... wire csr_match_uarch15 = (CSR_ADDR == 12'hccf); wire csr_we_uarch15 = CSR_WE & csr_match_uarch15; CSR_fflags u_csr_fflags (.CPU_CLK (CPU_CLK), .CPU_RESET (CPU_RESET), .CSR_WE (csr_we_fflags ), .DATA_IN (CSR_DATA_IN), .DATA_OUT (csr_fflags_data)); CSR_frm u_csr_frm (.CPU_CLK (CPU_CLK), .CPU_RESET (CPU_RESET), .CSR_WE (csr_we_frm ), .DATA_IN (CSR_DATA_IN), .DATA_OUT (csr_frm_data)); CSR_fcsr u_csr_fcsr (.CPU_CLK (CPU_CLK), .CPU_RESET (CPU_RESET), .CSR_WE (csr_we_fcsr ), .DATA_IN (CSR_DATA_IN), .DATA_OUT (csr_fcsr_data)); CSR_cycle u_csr_cycle (.CPU_CLK (CPU_CLK), .CPU_RESET (CPU_RESET), .CSR_WE (csr_we_cycle ), .DATA_IN (CSR_DATA_IN), .DATA_OUT (csr_cycle_data)); CSR_time u_csr_time (.CPU_CLK (CPU_CLK), .CPU_RESET (CPU_RESET), .CSR_WE (csr_we_time ), .DATA_IN (CSR_DATA_IN), .DATA_OUT (csr_time_data)); CSR_instret u_csr_instret (.CPU_CLK (CPU_CLK), .CPU_RESET (CPU_RESET), .CSR_WE (csr_we_instret ), .DATA_IN (CSR_DATA_IN), .DATA_OUT (csr_instret_data)); CSR_cycleh u_csr_cycleh (.CPU_CLK (CPU_CLK), .CPU_RESET (CPU_RESET), .CSR_WE (csr_we_cycleh ), .DATA_IN (CSR_DATA_IN), .DATA_OUT (csr_cycleh_data)); CSR_timeh u_csr_timeh (.CPU_CLK (CPU_CLK), .CPU_RESET (CPU_RESET), .CSR_WE (csr_we_timeh ), .DATA_IN (CSR_DATA_IN), .DATA_OUT (csr_timeh_data)); CSR_instreth u_csr_instreth (.CPU_CLK (CPU_CLK), .CPU_RESET (CPU_RESET), .CSR_WE (csr_we_instreth ), .DATA_IN (CSR_DATA_IN), .DATA_OUT (csr_instreth_data)); ... assign CSR_DATA_OUT = {32{csr_match_fflags}} & csr_fflags_data | {32{csr_match_frm}} & csr_frm_data | {32{csr_match_fcsr}} & csr_fcsr_data | {32{csr_match_cycle}} & csr_cycle_data | {32{csr_match_time}} & csr_time_data | {32{csr_match_instret}} & csr_instret_data | {32{csr_match_cycleh}} & csr_cycleh_data | {32{csr_match_timeh}} & csr_timeh_data | {32{csr_match_instreth}} & csr_instreth_data | {32{csr_match_sstatus}} & csr_sstatus_data | {32{csr_match_stvec}} & csr_stvec_data | {32{csr_match_sie}} & csr_sie_data | ...