您当前的位置: 首页 > 

耐心的小黑

暂无认证

  • 2浏览

    0关注

    323博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

chisel的信号名命名机制

耐心的小黑 发布时间:2021-07-11 17:06:58 ,浏览量:2

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             
关注
打赏
1640088279
查看更多评论
0.0398s