ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Chiselの文法 - 入門編 〜その9:I/Oポートのパラメタライズ〜

スポンサーリンク

Chiselの文法入門の続きで今回は第9回目
前回の終わりに書いたとおり、I/Oポートのパラメタライズについて
とは言いながら、I/O以外の例も示そうと思います。

Chisel入門編〜その9:回路のI/Oポートのパラメタライズ〜

I/Oのパラメタライズ

基本的な考え方はビット幅の調整と大きくは変わりません。
クラスパラメータとして受け取った変数の値を使って、生成するI/Oを変化させる形になります。
この際にScalaOption型のひとつであるSomeを使ってパラメタライズ対応したいポートをくるむ必要があることに注意が必要です。
例として書いてみると以下のような形になります。

class MyMod (hasInPort: Boolean, hasOutPort: Boolean) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(32.W))
    val in_opt = if (hasPort) Some(Input(UInt(32.W))) else None
    val out = Output(UInt(32.W))
    val out_opt = if (hasPort) Some(Output(UInt(32.W))) else None
  })
}

上記の例ではポートの切り替えにif式を使用していますが、match式を使用しても構いません。
繰り返しになりますがSomeでくるむのを忘れないように注意してください。

パラメタライズしたI/Oポートの使い方

次に最初の例で見たようなパラメタライズされたI/OポートをChiselの回路記述中で使用する方法についてを見ていきます。

Inputポート

引き続き先ほどの例を使用します。
下記のio.outにはio.inio.in_optを足した値を出力するようにした場合、以下のようになります。

class MyMod (hasInPort: Boolean, hasOutPort: Boolean) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(32.W))
    val in_opt = if (hasInPort) Some(Input(UInt(32.W))) else None
    val out = Output(UInt(32.W))
    val out_opt = if (hasOutPort) Some(Output(UInt(32.W))) else None
  })

  io.out := io.in + io.in_opt.getOrElse(0.U)
}
  • 生成されたRTL(hasInPort == false/hasOutPort == false)
module cmd2HelperMyMod( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [31:0] io_in, // @[:@6.4]
  output [31:0] io_out // @[:@6.4]
);
  wire [32:0] _T_10; // @[cmd2.sc 9:19:@8.4]
  wire [31:0] _T_11; // @[cmd2.sc 9:19:@9.4]
  // hasInPort == falseなので
  // io.in_optの代わりに0.Uが加算される
  assign _T_10 = io_in + 32'h0; // @[cmd2.sc 9:19:@8.4]
  assign _T_11 = _T_10[31:0]; // @[cmd2.sc 9:19:@9.4]
  assign io_out = _T_11;
endmodule
  • 生成されたRTL(hasInPort == true/hasOutPort == false)
module cmd4HelperMyMod( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [31:0] io_in, // @[:@6.4]
  input  [31:0] io_in_opt, // @[:@6.4]
  output [31:0] io_out // @[:@6.4]
);
  wire [32:0] _T_11; // @[cmd4.sc 9:19:@8.4]
  wire [31:0] _T_12; // @[cmd4.sc 9:19:@9.4]
  // io_in_optが加算される
  assign _T_11 = io_in + io_in_opt; // @[cmd4.sc 9:19:@8.4]
  assign _T_12 = _T_11[31:0]; // @[cmd4.sc 9:19:@9.4]
  assign io_out = _T_12;
endmodule

もしくは以下のようにhasInPortを使ってif式で記述を切り替える方法でもOKです。

class MyMod (hasInPort: Boolean, hasOutPort: Boolean) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(32.W))
    val in_opt = if (hasInPort) Some(Input(UInt(32.W))) else None
    val out = Output(UInt(32.W))
    val out_opt = if (hasOutPort) Some(Output(UInt(32.W))) else None
  })

  if (hasInPort) {
      io.out := io.in + io.in_opt.getOrElse(0.U)
  }
  else {
      io.out := io.in
  }
}
  • 生成されたRTL(hasInPort == false/hasOutPort == false)
module cmd3HelperMyMod( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [31:0] io_in, // @[:@6.4]
  output [31:0] io_out // @[:@6.4]
);
  // こちらはif式で切り替えたのでio_inのみになる
  assign io_out = io_in;
endmodule
  • 生成されたRTL(hasInPort == true/hasOutPort == false)
module cmd5HelperMyMod( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [31:0] io_in, // @[:@6.4]
  input  [31:0] io_in_opt, // @[:@6.4]
  output [31:0] io_out // @[:@6.4]
);
  wire [32:0] _T_11; // @[cmd5.sc 10:23:@8.4]
  wire [31:0] _T_12; // @[cmd5.sc 10:23:@9.4]
  // io_in_optが加算される
  assign _T_11 = io_in + io_in_opt; // @[cmd5.sc 10:23:@8.4]
  assign _T_12 = _T_11[31:0]; // @[cmd5.sc 10:23:@9.4]
  assign io_out = _T_12;
endmodule

Outputポート

続いてOutputポートですが、OutputポートはhasOutPort == trueのときにのみ、入力のio.inをそのまま接続することにします。
その場合には、以下のようにif式を使って回路が実体化する場合にのみ有効にif式内部がエラボレートの対象となるようにする必要があります
#この辺はVerilog HDLのgenerateを使った回路のパラメタライズと同様。

class MyMod (hasInPort: Boolean, hasOutPort: Boolean) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(32.W))
    val in_opt = if (hasInPort) Some(Input(UInt(32.W))) else None
    val out = Output(UInt(32.W))
    val out_opt = if (hasOutPort) Some(Output(UInt(32.W))) else None
  })

  if (hasInPort) {
      io.out := io.in + io.in_opt.getOrElse(0.U)
  }
  else {
      io.out := io.in
  }

  if (hasOutPort) {
      io.out_opt.get := io.in
  }
}

上記の回路においてhasOutPortの値をtrue/falseで変更してそれぞれRTLを生成したものが以下になります。

  • 生成されたRTL(hasInPort == true/hasOutPort == false)
// io_out_optポートは存在しない
module cmd6HelperMyMod( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [31:0] io_in, // @[:@6.4]
  input  [31:0] io_in_opt, // @[:@6.4]
  output [31:0] io_out // @[:@6.4]
);
  wire [32:0] _T_11; // @[cmd6.sc 10:23:@8.4]
  wire [31:0] _T_12; // @[cmd6.sc 10:23:@9.4]
  assign _T_11 = io_in + io_in_opt; // @[cmd6.sc 10:23:@8.4]
  assign _T_12 = _T_11[31:0]; // @[cmd6.sc 10:23:@9.4]
  assign io_out = _T_12;
endmodule
  • 生成されたRTL(hasInPort == false/hasOutPort == true)
module cmd7HelperMyMod( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [31:0] io_in, // @[:@6.4]
  output [31:0] io_out, // @[:@6.4]
  output [31:0] io_out_opt // @[:@6.4]
);
  assign io_out = io_in;
  // io_out_optポートが存在する
  assign io_out_opt = io_in;
endmodule

ここまでの例を振り返ってもらえるとわかるかと思いますが、Chiselのハードウェア要素が実体化する際に、ポートや回路の有無に関しての整合性が取れている必要があります。
またI/Oポートのパラメタライズの際にSomeでポート宣言をくるんでいるのでio.<ポート名>でアクセスした場合、そのデータはSomeインスタンスになっているため.getもしくはgetOrElseを呼び出してChiselのデータ型を取得する必要があることに注意が必要です。 hasOutPort == trueの例でお気づきとは思いますが、クラスパラメータを使って、if式やmatch式を使用することでI/Oポートだけではなく、モジュール内の回路記述もパラメタライズ出来ます。

Bundleを使ったI/Oポートのパラメタライズ

最後にBundleで束ねたChiselのデータ型を使ってパラメタライズを行う例を紹介しておきます。
これはデバッグポートのように、シミュレーション時には使用したいけど最終的には回路から消したいようなポートを作る際に有用です。

class DebugIO extends Bundle {
  val wrdataVec = Output(Vec(4, UInt(32.W)))
  val rddataVec = Output(Vec(4, UInt(32.W)))
}

class MyMem (debug: Boolean) extends Module {
  val io = IO(new Bundle {
    val addr = Input(UInt(32.W))
    val wren = Input(Bool())
    val wrdata = Input(UInt(32.W))
    val rddata = Output(UInt(32.W))
    val dbg = if (debug) Some(new DebugIO) else None
  })

  // メモリはUInt(8.W)x4で1wordのメモリ
  val m = Mem(256, Vec(4, UInt(8.W)))

  // 32bitを8bitx4のVecに変換
  val wrdata = Wire(Vec(4, UInt(8.W)))

  // 8bit毎にVecに格納
  // 2019/06/19 : コードがおかしかったので修正しました
  // とは言いながらもまだ実装の意図とは異なってます。
  // ちょうどいいので第10回でネタにします。
  for (i <- 0 until wrdata.length) {
    // wrdata(i) := io.wrdata(i + 7, i)
    wrdata(i) := io.wrdata((i * 8) + 7, i * 8)
  }

  when (io.wren) {
    m.write(io.addr, wrdata)
  }

  // rddataはMemがVec(4, UInt(8.W))なので
  // その形で返却される
  val rddata = m.read(io.addr)
  io.rddata := RegNext(Cat(rddata))

  // debug == trueの時のみ、debugポートに
  // read/writeデータを接続する
  if (debug) {
    val dbg = io.dbg.get

    dbg.wrdataVec := wrdata
    dbg.rddataVec := rddata
  }
}

上記をdebugの値をtrue/falseで変化させた場合に生成されるRTLは以下の様になります。
debug == trueの場合のI/Oポートとその端子へのassignに注目して確認してみてください。 #randomizeの記述は削除してあります。

  • 生成されたRTL(debug == false)
module cmd10HelperMyMem( // @[:@3.2]
  // I/Oポートにデバッグポートが存在しない
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [31:0] io_addr, // @[:@6.4]
  input         io_wren, // @[:@6.4]
  input  [31:0] io_wrdata, // @[:@6.4]
  output [31:0] io_rddata // @[:@6.4]
);
  reg [7:0] m_0 [0:255]; // @[cmd10.sc 15:14:@8.4]
  reg [31:0] _RAND_0;
  wire [7:0] m_0_rddata_data; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_0_rddata_addr; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_0__T_37_data; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_0__T_37_addr; // @[cmd10.sc 15:14:@8.4]
  wire  m_0__T_37_mask; // @[cmd10.sc 15:14:@8.4]
  wire  m_0__T_37_en; // @[cmd10.sc 15:14:@8.4]
  reg [7:0] m_1 [0:255]; // @[cmd10.sc 15:14:@8.4]
  reg [31:0] _RAND_1;
  wire [7:0] m_1_rddata_data; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_1_rddata_addr; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_1__T_37_data; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_1__T_37_addr; // @[cmd10.sc 15:14:@8.4]
  wire  m_1__T_37_mask; // @[cmd10.sc 15:14:@8.4]
  wire  m_1__T_37_en; // @[cmd10.sc 15:14:@8.4]
  reg [7:0] m_2 [0:255]; // @[cmd10.sc 15:14:@8.4]
  reg [31:0] _RAND_2;
  wire [7:0] m_2_rddata_data; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_2_rddata_addr; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_2__T_37_data; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_2__T_37_addr; // @[cmd10.sc 15:14:@8.4]
  wire  m_2__T_37_mask; // @[cmd10.sc 15:14:@8.4]
  wire  m_2__T_37_en; // @[cmd10.sc 15:14:@8.4]
  reg [7:0] m_3 [0:255]; // @[cmd10.sc 15:14:@8.4]
  reg [31:0] _RAND_3;
  wire [7:0] m_3_rddata_data; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_3_rddata_addr; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_3__T_37_data; // @[cmd10.sc 15:14:@8.4]
  wire [7:0] m_3__T_37_addr; // @[cmd10.sc 15:14:@8.4]
  wire  m_3__T_37_mask; // @[cmd10.sc 15:14:@8.4]
  wire  m_3__T_37_en; // @[cmd10.sc 15:14:@8.4]
  reg [31:0] _T_65; // @[cmd10.sc 28:23:@31.4]
  reg [31:0] _RAND_4;
  wire [7:0] wrdata_0; // @[cmd10.sc 20:27:@10.4]
  wire [7:0] wrdata_1; // @[cmd10.sc 20:27:@12.4]
  wire [7:0] wrdata_2; // @[cmd10.sc 20:27:@14.4]
  wire [7:0] wrdata_3; // @[cmd10.sc 20:27:@16.4]
  wire [7:0] _T_36; // @[:@19.6]
  wire [7:0] _T_49; // @[cmd10.sc 27:22:@26.4]
  wire [15:0] _T_61; // @[Cat.scala 30:58:@28.4]
  wire [15:0] _T_62; // @[Cat.scala 30:58:@29.4]
  wire [31:0] _T_63; // @[Cat.scala 30:58:@30.4]
  assign m_0_rddata_addr = _T_49;
  assign m_0_rddata_data = m_0[m_0_rddata_addr]; // @[cmd10.sc 15:14:@8.4]
  assign m_0__T_37_data = wrdata_0;
  assign m_0__T_37_addr = _T_36;
  assign m_0__T_37_mask = io_wren;
  assign m_0__T_37_en = io_wren;
  assign m_1_rddata_addr = _T_49;
  assign m_1_rddata_data = m_1[m_1_rddata_addr]; // @[cmd10.sc 15:14:@8.4]
  assign m_1__T_37_data = wrdata_1;
  assign m_1__T_37_addr = _T_36;
  assign m_1__T_37_mask = io_wren;
  assign m_1__T_37_en = io_wren;
  assign m_2_rddata_addr = _T_49;
  assign m_2_rddata_data = m_2[m_2_rddata_addr]; // @[cmd10.sc 15:14:@8.4]
  assign m_2__T_37_data = wrdata_2;
  assign m_2__T_37_addr = _T_36;
  assign m_2__T_37_mask = io_wren;
  assign m_2__T_37_en = io_wren;
  assign m_3_rddata_addr = _T_49;
  assign m_3_rddata_data = m_3[m_3_rddata_addr]; // @[cmd10.sc 15:14:@8.4]
  assign m_3__T_37_data = wrdata_3;
  assign m_3__T_37_addr = _T_36;
  assign m_3__T_37_mask = io_wren;
  assign m_3__T_37_en = io_wren;
  assign wrdata_0 = io_wrdata[7:0]; // @[cmd10.sc 20:27:@10.4]
  assign wrdata_1 = io_wrdata[8:1]; // @[cmd10.sc 20:27:@12.4]
  assign wrdata_2 = io_wrdata[9:2]; // @[cmd10.sc 20:27:@14.4]
  assign wrdata_3 = io_wrdata[10:3]; // @[cmd10.sc 20:27:@16.4]
  assign _T_36 = io_addr[7:0]; // @[:@19.6]
  assign _T_49 = io_addr[7:0]; // @[cmd10.sc 27:22:@26.4]
  assign _T_61 = {m_2_rddata_data,m_3_rddata_data}; // @[Cat.scala 30:58:@28.4]
  assign _T_62 = {m_0_rddata_data,m_1_rddata_data}; // @[Cat.scala 30:58:@29.4]
  assign _T_63 = {_T_62,_T_61}; // @[Cat.scala 30:58:@30.4]
  assign io_rddata = _T_65;

  always @(posedge clock) begin
    if(m_0__T_37_en & m_0__T_37_mask) begin
      m_0[m_0__T_37_addr] <= m_0__T_37_data; // @[cmd10.sc 15:14:@8.4]
    end
    if(m_1__T_37_en & m_1__T_37_mask) begin
      m_1[m_1__T_37_addr] <= m_1__T_37_data; // @[cmd10.sc 15:14:@8.4]
    end
    if(m_2__T_37_en & m_2__T_37_mask) begin
      m_2[m_2__T_37_addr] <= m_2__T_37_data; // @[cmd10.sc 15:14:@8.4]
    end
    if(m_3__T_37_en & m_3__T_37_mask) begin
      m_3[m_3__T_37_addr] <= m_3__T_37_data; // @[cmd10.sc 15:14:@8.4]
    end
    _T_65 <= _T_63;
  end
endmodule
  • 生成されたRTL(debug == true)
module cmd12HelperMyMem( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [31:0] io_addr, // @[:@6.4]
  input         io_wren, // @[:@6.4]
  input  [31:0] io_wrdata, // @[:@6.4]
  output [31:0] io_rddata, // @[:@6.4]
  ////////
  // デバッグポートが生成された
  ////////
  output [7:0]  io_dbg_wrdataVec_0, // @[:@6.4]
  output [7:0]  io_dbg_wrdataVec_1, // @[:@6.4]
  output [7:0]  io_dbg_wrdataVec_2, // @[:@6.4]
  output [7:0]  io_dbg_wrdataVec_3, // @[:@6.4]
  output [7:0]  io_dbg_rddataVec_0, // @[:@6.4]
  output [7:0]  io_dbg_rddataVec_1, // @[:@6.4]
  output [7:0]  io_dbg_rddataVec_2, // @[:@6.4]
  output [7:0]  io_dbg_rddataVec_3 // @[:@6.4]
);
  reg [7:0] m_0 [0:255]; // @[cmd12.sc 15:14:@8.4]
  reg [31:0] _RAND_0;
  wire [7:0] m_0_rddata_data; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_0_rddata_addr; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_0__T_73_data; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_0__T_73_addr; // @[cmd12.sc 15:14:@8.4]
  wire  m_0__T_73_mask; // @[cmd12.sc 15:14:@8.4]
  wire  m_0__T_73_en; // @[cmd12.sc 15:14:@8.4]
  reg [7:0] m_1 [0:255]; // @[cmd12.sc 15:14:@8.4]
  reg [31:0] _RAND_1;
  wire [7:0] m_1_rddata_data; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_1_rddata_addr; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_1__T_73_data; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_1__T_73_addr; // @[cmd12.sc 15:14:@8.4]
  wire  m_1__T_73_mask; // @[cmd12.sc 15:14:@8.4]
  wire  m_1__T_73_en; // @[cmd12.sc 15:14:@8.4]
  reg [7:0] m_2 [0:255]; // @[cmd12.sc 15:14:@8.4]
  reg [31:0] _RAND_2;
  wire [7:0] m_2_rddata_data; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_2_rddata_addr; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_2__T_73_data; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_2__T_73_addr; // @[cmd12.sc 15:14:@8.4]
  wire  m_2__T_73_mask; // @[cmd12.sc 15:14:@8.4]
  wire  m_2__T_73_en; // @[cmd12.sc 15:14:@8.4]
  reg [7:0] m_3 [0:255]; // @[cmd12.sc 15:14:@8.4]
  reg [31:0] _RAND_3;
  wire [7:0] m_3_rddata_data; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_3_rddata_addr; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_3__T_73_data; // @[cmd12.sc 15:14:@8.4]
  wire [7:0] m_3__T_73_addr; // @[cmd12.sc 15:14:@8.4]
  wire  m_3__T_73_mask; // @[cmd12.sc 15:14:@8.4]
  wire  m_3__T_73_en; // @[cmd12.sc 15:14:@8.4]
  reg [31:0] _T_101; // @[cmd12.sc 28:23:@31.4]
  reg [31:0] _RAND_4;
  wire [7:0] wrdata_0; // @[cmd12.sc 20:27:@10.4]
  wire [7:0] wrdata_1; // @[cmd12.sc 20:27:@12.4]
  wire [7:0] wrdata_2; // @[cmd12.sc 20:27:@14.4]
  wire [7:0] wrdata_3; // @[cmd12.sc 20:27:@16.4]
  wire [7:0] _T_72; // @[:@19.6]
  wire [7:0] _T_85; // @[cmd12.sc 27:22:@26.4]
  wire [15:0] _T_97; // @[Cat.scala 30:58:@28.4]
  wire [15:0] _T_98; // @[Cat.scala 30:58:@29.4]
  wire [31:0] _T_99; // @[Cat.scala 30:58:@30.4]
  assign m_0_rddata_addr = _T_85;
  assign m_0_rddata_data = m_0[m_0_rddata_addr]; // @[cmd12.sc 15:14:@8.4]
  assign m_0__T_73_data = wrdata_0;
  assign m_0__T_73_addr = _T_72;
  assign m_0__T_73_mask = io_wren;
  assign m_0__T_73_en = io_wren;
  assign m_1_rddata_addr = _T_85;
  assign m_1_rddata_data = m_1[m_1_rddata_addr]; // @[cmd12.sc 15:14:@8.4]
  assign m_1__T_73_data = wrdata_1;
  assign m_1__T_73_addr = _T_72;
  assign m_1__T_73_mask = io_wren;
  assign m_1__T_73_en = io_wren;
  assign m_2_rddata_addr = _T_85;
  assign m_2_rddata_data = m_2[m_2_rddata_addr]; // @[cmd12.sc 15:14:@8.4]
  assign m_2__T_73_data = wrdata_2;
  assign m_2__T_73_addr = _T_72;
  assign m_2__T_73_mask = io_wren;
  assign m_2__T_73_en = io_wren;
  assign m_3_rddata_addr = _T_85;
  assign m_3_rddata_data = m_3[m_3_rddata_addr]; // @[cmd12.sc 15:14:@8.4]
  assign m_3__T_73_data = wrdata_3;
  assign m_3__T_73_addr = _T_72;
  assign m_3__T_73_mask = io_wren;
  assign m_3__T_73_en = io_wren;
  assign wrdata_0 = io_wrdata[7:0]; // @[cmd12.sc 20:27:@10.4]
  assign wrdata_1 = io_wrdata[8:1]; // @[cmd12.sc 20:27:@12.4]
  assign wrdata_2 = io_wrdata[9:2]; // @[cmd12.sc 20:27:@14.4]
  assign wrdata_3 = io_wrdata[10:3]; // @[cmd12.sc 20:27:@16.4]
  assign _T_72 = io_addr[7:0]; // @[:@19.6]
  assign _T_85 = io_addr[7:0]; // @[cmd12.sc 27:22:@26.4]
  assign _T_97 = {m_2_rddata_data,m_3_rddata_data}; // @[Cat.scala 30:58:@28.4]
  assign _T_98 = {m_0_rddata_data,m_1_rddata_data}; // @[Cat.scala 30:58:@29.4]
  assign _T_99 = {_T_98,_T_97}; // @[Cat.scala 30:58:@30.4]
  assign io_rddata = _T_101;

  ////////
  // デバッグポートにデータが接続された
  ////////
  assign io_dbg_wrdataVec_0 = wrdata_0;
  assign io_dbg_wrdataVec_1 = wrdata_1;
  assign io_dbg_wrdataVec_2 = wrdata_2;
  assign io_dbg_wrdataVec_3 = wrdata_3;
  assign io_dbg_rddataVec_0 = m_0_rddata_data;
  assign io_dbg_rddataVec_1 = m_1_rddata_data;
  assign io_dbg_rddataVec_2 = m_2_rddata_data;
  assign io_dbg_rddataVec_3 = m_3_rddata_data;

  always @(posedge clock) begin
    if(m_0__T_73_en & m_0__T_73_mask) begin
      m_0[m_0__T_73_addr] <= m_0__T_73_data; // @[cmd12.sc 15:14:@8.4]
    end
    if(m_1__T_73_en & m_1__T_73_mask) begin
      m_1[m_1__T_73_addr] <= m_1__T_73_data; // @[cmd12.sc 15:14:@8.4]
    end
    if(m_2__T_73_en & m_2__T_73_mask) begin
      m_2[m_2__T_73_addr] <= m_2__T_73_data; // @[cmd12.sc 15:14:@8.4]
    end
    if(m_3__T_73_en & m_3__T_73_mask) begin
      m_3[m_3__T_73_addr] <= m_3__T_73_data; // @[cmd12.sc 15:14:@8.4]
    end
    _T_101 <= _T_99;
  end
endmodule

ということで、I/Oポートをパラメタライズする方法でした。
ご覧頂いたとおりBundleと組み合わせることで、色々柔軟にパラメタライズすることが可能です。
個人的な意見ですがScalaの構文を使って回路記述を切り分けるので、Chiselとしての評価のタイミング時に回路の記述が確定していることを念頭においてパラメタライズ方法を考えるのが良いと思っています。
ここで紹介したのは基本的な例と考え方なので、色々試してみてはいかがでしょうか? 次回は筆者が個人的にChiselの魅力的な機能の一つだと考えているテスト記述について取り上げます。入門編としては次回が最後になる予定です。
ご興味ある方は最後までお付き合いください。