Chiselを使うと柔軟性の高いモジュール設計が可能となる。一つの手法として、パラメータとしてクラスを渡す手法について調査した。
例えば、RISC-Vの1モジュールのPMP(Physical Memory Protection)の簡単な機能について実装を考えてみる。ただし今回はどのメモリ領域を保護するかについてはパラメータで指定するものとし、制御レジスタなどは存在しないものとする。
まず、コンフィギュレーションできる領域を考える。まず、通貨できる領域の数と、その領域のアドレス、アドレスの広さを調整できるものとする。
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して、アクセス可能かを決定する。
最後に、パラメータの設定方法だが、そもそもAddressCfgBase
がabstract 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に変換されていることが確認できた。