FPGA開発日記

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

Chiselを使ってコンフィギャラブルなモジュール設計を行う

Chiselを使うと柔軟性の高いモジュール設計が可能となる。一つの手法として、パラメータとしてクラスを渡す手法について調査した。

例えば、RISC-Vの1モジュールのPMP(Physical Memory Protection)の簡単な機能について実装を考えてみる。ただし今回はどのメモリ領域を保護するかについてはパラメータで指定するものとし、制御レジスタなどは存在しないものとする。

f:id:msyksphinz:20190812002848p:plain
作成するMemory Protection Unit

まず、コンフィギュレーションできる領域を考える。まず、通貨できる領域の数と、その領域のアドレス、アドレスの広さを調整できるものとする。

abstract class AddressCfgBase {
  val NumOfPmp : Int

  val Base   : Array[Int]
  val Length : Array[Int]
}

Scalaで上記のようなAbstract Classを作成した。NumOfPmpは設定できる領域の数。Baseはベースアドレス、そしてBase+Lengthまでがアクセスできる領域である。

このクラスをパラメータとして渡して、PMPを実装してみる。

class pmp_simple(cfg: AddressCfgBase) extends Module
{
  val io = IO(new Bundle {
    val vld  = Input(Bool())
    val addr = Input(UInt(32.W))

    val en   = Output(Bool())
  })

  def check_region(addr: UInt, base: Int, length: Int) : Bool = {
    return Mux(addr >= base.U(64.W) && addr < (base + length).U(64.W), true.B, false.B)
  }

  val en_vec = Wire(Vec(cfg.NumOfPmp, Bool()))
  for (i <- 0 until cfg.NumOfPmp) {
    en_vec(i) := Mux(io.vld, check_region(io.addr, cfg.Base(i), cfg.Length(i)), false.B)
  }
  io.en := en_vec.asUInt.andR
}

cfg.NumOfPmp個の領域を個別にチェックする。チェックにはcheck_region()関数を使ってチェックする。 最後に、すべての領域チェックの結果をORして、アクセス可能かを決定する。

最後に、パラメータの設定方法だが、そもそもAddressCfgBaseabstract classなので、実体のクラスを作らなければならない。以下のようにして、実体のクラスを作成する。

  class pmp_cfg1 extends AddressCfgBase {
    val NumOfPmp = 4

    val Base   = Array(0x1000, 0x2000, 0x40000000, 0x60000000)
    val Length = Array(0x4,    0x0010, 0x10000000, 0x1000    )
  }

  val cfg1 = new pmp_cfg1
  class pmp_cfg2 extends AddressCfgBase {
    val NumOfPmp = 6

    val Base   = Array(0x0,  0x400, 0xff0000, 0xfe00000, 0xffffff, 0x11111)
    val Length = Array(0x10, 0x40,  0x10000,  0x100000 , 0x1,      0x11111)
  }

  val cfg2 = new pmp_cfg2

それぞれ、pmp_simpleクラスをVerilogに変換する。

  chisel3.Driver.execute(args, () => new pmp_simple(cfg1))
  chisel3.Driver.execute(args, () => new pmp_simple(cfg2))

それぞれ生成されたVerilogを確認する。

module pmp_simple( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input         io_vld, // @[:@6.4]
  input  [31:0] io_addr, // @[:@6.4]
  output        io_en // @[:@6.4]
);
...
  assign _GEN_0 = {{32'd0}, io_addr}; // @[pmp_simple.scala 26:21:@9.4]
  assign _T_21 = _GEN_0 >= 64'h1000; // @[pmp_simple.scala 26:21:@9.4]
  assign _T_23 = _GEN_0 < 64'h1004; // @[pmp_simple.scala 26:45:@10.4]
  assign _T_24 = _T_21 & _T_23; // @[pmp_simple.scala 26:37:@11.4]
  assign en_vec_0 = io_vld ? _T_24 : 1'h0; // @[pmp_simple.scala 31:21:@13.4]
  assign _T_31 = _GEN_0 >= 64'h2000; // @[pmp_simple.scala 26:21:@15.4]
  assign _T_33 = _GEN_0 < 64'h2010; // @[pmp_simple.scala 26:45:@16.4]
  assign _T_34 = _T_31 & _T_33; // @[pmp_simple.scala 26:37:@17.4]
  assign en_vec_1 = io_vld ? _T_34 : 1'h0; // @[pmp_simple.scala 31:21:@19.4]
  assign _T_41 = _GEN_0 >= 64'h40000000; // @[pmp_simple.scala 26:21:@21.4]
  assign _T_43 = _GEN_0 < 64'h50000000; // @[pmp_simple.scala 26:45:@22.4]
  assign _T_44 = _T_41 & _T_43; // @[pmp_simple.scala 26:37:@23.4]
  assign en_vec_2 = io_vld ? _T_44 : 1'h0; // @[pmp_simple.scala 31:21:@25.4]
  assign _T_51 = _GEN_0 >= 64'h60000000; // @[pmp_simple.scala 26:21:@27.4]
  assign _T_53 = _GEN_0 < 64'h60001000; // @[pmp_simple.scala 26:45:@28.4]
  assign _T_54 = _T_51 & _T_53; // @[pmp_simple.scala 26:37:@29.4]
  assign en_vec_3 = io_vld ? _T_54 : 1'h0; // @[pmp_simple.scala 31:21:@31.4]
  assign _T_62 = {en_vec_3,en_vec_2,en_vec_1,en_vec_0}; // @[pmp_simple.scala 33:19:@35.4]
  assign io_en = _T_62 != 4'h0; // @[pmp_simple.scala 33:9:@37.4]

上記のように、パラメータを使って記述したChiselのコードがVerilogに変換されていることが確認できた。