chisel的信号名命名机制

2021.7.25 更新,纠正了之前的很多错误,以这版为准!!!


一、前言

Chisel向来很难可靠地捕捉信号的名称。造成这种情况的原因是:

  • 主要依靠反射查找名称
  • 使用@chiselName宏,该宏具有不可靠的行为

Chisel 3.4引入了一个自定义Scala编译器插件,它允许在声明信号名时实现可靠和自动的捕获。此外,该版本还包括大量使用的一个新的 prefixing API,该API可以更稳定地命名通过函数调用以编程方式生成的信号。

二、编译器插件

Chisel 3.4中,我们可以在build.sbt文件中添加上下面这一行,以改善信号的命名。

// chiselVersion is the String version (eg. "3.4.0")
addCompilerPlugin("edu.berkeley.cs" % "chisel3-plugin" % chiselVersion cross CrossVersion.full)

这个插件将在Scala编译器的typer阶段之后运行。它查找任何形式为val x = y的用户代码,其中x的类型为chisel3.Data、chisel3.MemBase或chisel3.experimental.BaseModule。对于符合这一标准的每一行,它都会重写这一行。在下面的例子中,注释行就是上面重写的行。


  • 第一种情况:如果形式为val x = y的用户代码位于bundle声明中,或者是用于模块实例化,那么右侧就会被调用autoNameRecurally的语句重写(如下例中的注释行),该调用用于命名信号/模块。
class MyBundle extends Bundle {
  val foo = Input(UInt(3.W))
  // val foo = autoNameRecursively("foo")(Input(UInt(3.W)))
}
class Example1 extends MultiIOModule {
  val io = IO(new MyBundle())
  // val io = autoNameRecursively("io")(IO(new MyBundle()))
  ......
}
class Example2 extends MultiIOModule {
  val io = IO(new MyBundle())
  // val io = autoNameRecursively("io")(IO(new MyBundle()))
  val mymodule = Module(new Example1)
  // val mymodule = autoNameRecursively("mymodule")(Module(new Example1))
  ......
}

注意,如果端口使用了bundle,即使你定义端口时变量名是io,但是端口名会是io_foo的组合,而不是单一的自定义的变量名;而模块例化的名字不会变,就是你定义的变量名。如下所示:

module Example2(
  input        clock,
  input        reset,
  input  [2:0] io_foo
);

Example1 mymodule(
	.x(),
	.y(),
	.z()
) 
endmodule
  • 第二种情况:如果形式为val x = y的用户代码所处的位置不属于第一种情况,那么情况会有所不同。右侧也将被重写(如下例中的注释行),左侧定义的变量名会作为前缀添加到由右侧y声明左侧x时产成的任何中间变量的名称中:
class Example3 extends MultiIOModule {
  val in = IO(Input(UInt(2.W)))
  // val in = autoNameRecursively("in")(prefix("in")(IO(Input(UInt(2.W)))))

  val out = IO(Output(UInt(2.W)))
  // val out = autoNameRecursively("out")(prefix("out")(IO(Output(UInt(2.W)))))

  def inXin() = in * in

  val add = 3.U + inXin()
  // val add = autoNameRecursively("add")(prefix("add")(3.U + inXin()))
  // Note that the intermediate result of the multiplication is prefixed with `add`

  out := add + 1.U
}
module Example3(
  input        clock,
  input        reset,
  input  [1:0] in,
  output [1:0] out
);
  wire [3:0] _add_T = in * in; // @[naming.md 48:20]
  wire [3:0] add = 4'h3 + _add_T; // @[naming.md 50:17]
  wire [3:0] _out_T_1 = add + 4'h1; // @[naming.md 54:14]
  assign out = _out_T_1[1:0]; // @[naming.md 54:7]
endmodule

该插件的作用就是给中间变量加前缀,个人觉得这样做的好处是可以让阅读者更加清晰的知道这些中间变量是为了生成哪个val变量而产生的!!!如果没有该插件,那么中间变量的命名将都是类似_T_i的名称,就会很乱。

至于中间变量的产生机制,可以多看例子总结一下规律,但其实没有太大影响,因为它会把一个式子分成多次计算,也就导致了中间变量的产生。

还有一个非常重要的点,就是使用val定义左侧x的优化和命名问题:

  • 1、使用val定义的wire类型的变量,或者说是非reg类型的变量,如果该变量出现在了等号右边,并且是参与了计算,而且对输出信号有影响,那么在生成的verilog中就不会把它优化掉,而是生成一个名字一样的wire信号,如下例所示:Example4_1的verilog中有add信号,这是因为最后一句代码是out := add + 1.U,add不仅出现在了右侧,并且参与了计算。而Example4_2中却没有add信号,是因为它虽然出现在了右侧,但只是赋值而已,并没有参与计算。
class Example4_1 extends MultiIOModule {
  val in = IO(Input(UInt(2.W)))
  val out = IO(Output(UInt()))

  val add = in + in + in

  out := add + 1.U
}

class Example4_2 extends MultiIOModule {
  val in = IO(Input(UInt(2.W)))
  val out = IO(Output(UInt()))

  val add = in + in + in

  out := add
}

生成的verilog代码是:

module Example4_1(
  input        clock,
  input        reset,
  input  [1:0] in,
  output [1:0] out
);
  wire [1:0] _add_T_1 = in + in; // @[naming.md 115:16]
  wire [1:0] add = _add_T_1 + in; // @[naming.md 115:21]
  assign out = add + 2'h1; // @[naming.md 117:14]
endmodule

module Example4_2(
  input        clock,
  input        reset,
  input  [1:0] in,
  output [1:0] out
);
  wire [1:0] _add_T_1 = in + in; // @[Name3.scala 31:16]
  assign out = _add_T_1 + in; // @[Name3.scala 31:21]
endmodule
  • 2、使用val定义的reg类型的变量,无论是否参与计算,只要是对输出信号有影响,一般都不会被优化掉,而且信号名和chisel中的变量名一样。

以下例子,如果不特意说明的话,都是默认添加了chisel3-plugin插件

三、prefix和noPrefix

  • 如上所示,编译器插件会自动尝试为您添加一些信号的前缀。但是,作为用户,您也可以使用prefix添加自己的前缀。这尤其适用于生态型修复,您需要在模块中添加一些逻辑,但不想影响模块中的其他名称。看下面两个代码:
class Example5 extends MultiIOModule {
  val in = IO(Input(UInt(2.W)))
  val out = IO(Output(UInt()))

  val add = in + in + in

  out := add + 1.U
}


class Example6 extends MultiIOModule {
  val in = IO(Input(UInt(2.W)))
  val out = IO(Output(UInt()))

  val add = in + in + in

  out := prefix("ECO") { add + 1.U + in }
}
module Example5(
  input        clock,
  input        reset,
  input  [1:0] in,
  output [1:0] out
);
  wire [1:0] _add_T_1 = in + in; // @[naming.md 115:16]
  wire [1:0] add = _add_T_1 + in; // @[naming.md 115:21]
  assign out = add + 2'h1; // @[naming.md 117:14]
endmodule

module Example6(
  input        clock,
  input        reset,
  input  [1:0] in,
  output [1:0] out
);
  wire [1:0] _add_T_1 = in + in; // @[naming.md 125:16]
  wire [1:0] add = _add_T_1 + in; // @[naming.md 125:21]
  wire [1:0] _out_ECO_T_1 = add + 2'h1; // @[naming.md 127:30]
  assign out = _out_ECO_T_1 + in; // @[naming.md 127:36]
endmodule

可以看到,设置了前缀ECO添加到了out相关的中间变量的名字中,并且是在编译器插件添加的前缀out的后面。

  • 有时希望禁用前缀,在这种情况下,可以使用noPrefix对象:
class Example7 extends MultiIOModule {
  val in = IO(Input(UInt(2.W)))
  val out = IO(Output(UInt()))

  val add = noPrefix { in + in + in }

  out := add
}
module Example7(
  input        clock,
  input        reset,
  input  [1:0] in,
  output [1:0] out
);
  wire [1:0] _T_1 = in + in; // @[naming.md 166:27]
  assign out = _T_1 + in; // @[naming.md 166:32]
endmodule

四、.suggestName 方法

如果要指定信号的名称,可以使用.suggestNameAPI。注意这是用来指定信号的名称的。

class Example8 extends MultiIOModule {
  val in = IO(Input(UInt(2.W)))
  val out = IO(Output(UInt()))

  val add = (in + (in + in).suggestName("foo"))

  out := add + 1.U
}
module Example8(
  input        clock,
  input        reset,
  input  [1:0] in,
  output [1:0] out
);
  wire [1:0] _add_T_1 = in + in; // @[Name2.scala 16:23]
  wire [1:0] foo = in + _add_T_1; // @[Name2.scala 16:17]
  assign out = foo + 2'h1; // @[Name2.scala 18:14]
endmodule

五、@chiselname

这个宏其实现在已经不被推荐使用了,因为一开始就说过它不太可靠,而且它的功能完全可以被chisel3-plugin插件取代。但还是通过一个例子简单提一下它的使用方法,注意这里不添加chisel3-plugin插件!!!它的作用主要有以下两点:

  • 消除嵌套函数或者作用域对val变量命名的影响:嵌套函数或者作用域会使得内部使用val定义的左侧x变量在verilog中对应的信号的名称和在chisel中定义的不一样。
  • 将创建的类对象中的变量加上对象名作为前缀(参考六中的例子)

注意,它不会对各种中间变量添加前缀,它的作用目前来看就是上面所说的两种!!!

一般来说,在嵌套函数或者作用域中的val变量,在转换成Verilog时不会生成正确的变量名。例如:

// name.scala
package test
 
import chisel3._
 
class Example9_1 extends Module {
  val io = IO(new Bundle {
    val a = Input(Bool())
    val b = Output(UInt(4.W))
  })
  when (io.a) {
    val innerReg = RegInit(5.U(4.W))
    innerReg := innerReg + 1.U
    io.b := innerReg
  } .otherwise {
    io.b := 10.U
  }
}

它对应生成的Verilog为:

// TestMod.v
module Example9_1(
  input        clock,
  input        reset,
  input        io_a,
  output [3:0] io_b
);
  reg [3:0] _T;
  wire [3:0] _T_2;
  assign _T_2 = _T + 4'h1;
  assign io_b = io_a ? _T : 4'ha;
  always @(posedge clock) begin
    if (reset) begin
      _T <= 4'h5;
    end else begin
      _T <= _T_2;
    end
  end
endmodule

注意看,when语句块里声明的寄存器innerReg,被命名成了“_T”

如果想让名字正确,则需要在build.sbt文件里加上:

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full) 

同时,设计代码里需要加上传递给Firrtl的注解:

...
import chisel3._
import chisel3.experimental.chiselName

@chiselName
class Example9_2 extends Module {
...

这样,对应的Verilog文件就有了正确的寄存器名字:

// TestMod.v
module Example9_2 (
  input        clock,
  input        reset,
  input        io_a,
  output [3:0] io_b
);
  reg [3:0] innerReg;
  wire [3:0] _T_1;
  assign _T_1 = innerReg + 4'h1;
  assign io_b = io_a ? innerReg : 4'ha;
  always @(posedge clock) begin
    if (reset) begin
      innerReg <= 4'h5;
    end else begin
      innerReg <= _T_1;
    end
  end
endmodule

其实即使不加@chiselname,只需要添加上chisel3-plugin插件,innerReg也会出现。因为该插件除了添加前缀,还有一个功能就是将嵌套函数或者作用域对使用val定义的左侧x变量命名的影响给去除。

六、特质NoChiselNamePrefix

混入该特质之后,可以禁止chisel在命名的时候给对象内部的变量的名字添加对象名作为前缀,注意该特质只能在创建对象时混入,不能给普通变量混入。

但注意要和@chiselname一起使用,之所以这么说是因为@chiselname会给对象内部的变量加上对象名称作为前缀,而chisel3-plugin插件不会这么命名,它会将counter0和counter1 中的myReg分别命名为myReg和myReg_1,此时加不加NoChiselNamePrefix都一样。

import chisel3._
import chisel3.experimental.{chiselName, NoChiselNamePrefix}

// Note that this is not a Module
@chiselName
class Counter(w: Int) {
  val myReg = RegInit(0.U(w.W))
  myReg := myReg + 1.U
}

@chiselName
class MyModule extends Module {
  val io = IO(new Bundle {
    val out = Output(UInt(8.W))
  })
  // Name of myReg will be "counter0_myReg"
  val counter0 = new Counter(8)
  // Name of myReg will be "myReg"
  val counter1 = new Counter(8) with NoChiselNamePrefix
  io.out := counter0.myReg + counter1.myReg
}

七、单例对象forceName

该单例对象也可以修改信号或者例化的模块的名字,使用也比较简单,传入想修改名字的信号或者例化的模块以及一个字符串即可。只需要注意入参的类型限制即可,必须是chisel3.Element或者chisel3.experimental.BaseModule的子类,简单看下apply方法:


  /** Force the name of this signal
    *
    * @param signal Signal to name
    * @param name Name to force to
    */
  def apply[T <: chisel3.Element](signal: T, name: String): T

  /** Force the name of this instance to the name its given during Chisel compilation
    *
    * @param instance Instance to name
    */
  def apply(instance: chisel3.experimental.BaseModule, name: String): Unit

举例:

  • 修改信号的名字
class Example10 extends MultiIOModule {
  val io = IO(new Bundle {
    val in = Input(UInt(2.W))
    val out1 = Output(UInt())
    val out2 = Output(UInt())
  })

  val reg = RegNext(io.in)
  forceName(reg,"myReg")
  
  val add = ( io.in + (io.in + io.in))
  forceName(io.in,"inname")
  forceName(add,"myadd")//不起作用,因为add会被优化掉
  
  io.out1 := add
  forceName(io.out1,"out1name")
  
  io.out2 := reg
  forceName(io.out2,"out2name")
}
module Example10(
  input        clock,
  input        reset,
  input  [1:0] inname,
  output [1:0] out1name,
  output [1:0] out2name
);
`ifdef RANDOMIZE_REG_INIT
  reg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INIT
  reg [1:0] myReg; // @[Name.scala 16:20]
  wire [1:0] _T_1 = inname + inname; // @[Name.scala 18:30]
  assign out1name = inname + _T_1; // @[Name.scala 18:21]
  assign out2name = myReg; // @[Name.scala 24:11]
  always @(posedge clock) begin
    myReg <= inname; // @[Name.scala 16:20]
  end
endmodule

需要注意的是,在修改信号的名字时,如果信号会被优化掉,那么该API就不起作用了。

  • 修改例化的模块的名字
val arbiter = Module(new Arbiter(UInt(8.W), 2))  // 2 to 1 Priority Arbiter
forceName(arbiter,"forceName_arbiter")

Arbiter forceName_arbiter ( // @[Arbiter.scala 38:23]
    .io_in_0_ready(forceName_arbiter_io_in_0_ready),
    .io_in_0_valid(forceName_arbiter_io_in_0_valid),
    .io_in_0_bits(forceName_arbiter_io_in_0_bits),
    .io_in_1_ready(forceName_arbiter_io_in_1_ready),
    .io_in_1_valid(forceName_arbiter_io_in_1_valid),
    .io_in_1_bits(forceName_arbiter_io_in_1_bits),
    .io_out_ready(forceName_arbiter_io_out_ready),
    .io_out_valid(forceName_arbiter_io_out_valid),
    .io_out_bits(forceName_arbiter_io_out_bits),
    .io_chosen(forceName_arbiter_io_chosen)
  );

参考链接https://www.chisel-lang.org/chisel3/docs/explanations/naming.html

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

耐心的小黑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值