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
方法
如果要指定信号的名称,可以使用.suggestName
API。注意这是用来指定信号的名称的。
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