FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

CSRのVerilogモデルを自動生成するためのジェネレータ

http://bar.eecs.berkeley.edu/projects/images/projects/2010-riscv.png

RISC-V対応のパイプラインプロセッサをちまちま作ってみている。基本的な命令発行制御などはできるようになってきたが、プロセッサとしてベンチマークなどを動作させるために必要になってくるのは、やはりシステムレジスタの実装だ。

RISC-Vのシステムレジスタは、Privileged Architecture Manualに纏まっており、権限ごとにアクセスできるシステムレジスタが固まっている。 また、命令からこれらのシステムレジスタにアクセスするためには、システムレジスタごとに割り当てられているアドレスを使って読み書きを行う。

これらをアウトオブオーダのプロセッサに組み込む場合にはどうしたらいいのだろう? 汎用プロセッサの場合にはリネームマップを使って32本のアーキテクチャレジスタから物理レジスタへのマッピングをするが、これに追加してシステムレジスタ用のリネームマップを作るか? それはとても無駄に思える。それは、システムレジスタにアクセスするのはかなり稀であり、そのために巨大なリネームマップを用意するのはコストに見合わない。

したがって、システムレジスタのアクセスは通常のパイプラインプロセッサによるアクセスと同様、IDは振らずに通常のアクセスとして扱うか、さらに扱いを下げて、フォワーディングもしない、つまりシステムレジスタの実体に最新の値が書き込まれるまでは、後続のシステムレジスタにアクセスする命令はストップする、というフォワーディングなしのパイプラインと同様な動きをさせる。

これにより、万が一システムレジスタへアクセスが頻発するプログラムの場合は性能は激落ちとなるが、そんなプログラムは滅多にないと仮定して、このアイデアを採用することにする。

CSRレジスタVerilog実装を、ISSから自動生成する

ところで、CSRレジスタ(RISC-Vのシステムレジスタ)は非常に多く、それぞれで書き込みができる権限、書き込みできるフィールドも異なる。 これらについてひとつづつマニュアルで実装していたら日が暮れるので、ISSが持っているRubyのテーブルからCSRのアクセスクラスを自動的に生成する機構をVerilog-HDLに発展させる。

github.com

自作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 |
...

とりあえずここまでで、CSRのモジュールは完成した。次は、これをパイプラインプロセッサに接続しよう。