从历史上看,Chisel 在可靠地捕获信号名称方面遇到了麻烦。造成这种情况的原因是
(1)主要依靠反射来查找名称
(2)使用@chiselName
行为不可靠的宏。
Chisel 3.4 引入了一个自定义的 Scala 编译器插件,它可以在声明信号名称时实现可靠和自动捕获信号名称。此外,此版本还大量使用了新的前缀 API,它可以更稳定地命名从函数调用以编程方式生成的信号。
本文档解释了现在在 Chisel 中如何命名信号和模块名称。有关如何解决系统名称稳定性问题的手册示例,请参阅命名手册。
编译器插件
// Imports used by the following examples
import chisel3._
import chisel3.experimental.{prefix, noPrefix}
import chisel3.stage.ChiselStage
随着 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
。对于符合此标准的每一行,它都会重写该行。在以下示例中,注释行是上面的行被重写的内容。
如果该行在捆绑声明中或者是模块实例化,则将其重写以将右侧替换为对 的调用autoNameRecursively
,该调用命名信号/模块。
class MyBundle extends Bundle {
val foo = Input(UInt(3.W))
// val foo = autoNameRecursively("foo")(Input(UInt(3.W)))
}
class Example1 extends Module {
val io = IO(new MyBundle())
// val io = autoNameRecursively("io")(IO(new MyBundle()))
}
module Example1(
input clock,
input reset,
input [2:0] io_foo
);
endmodule
否则,它会被重写以包含名称作为在执行 val 声明右侧时生成的任何信号的前缀:
class Example2 extends Module {
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 Example2(
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
请注意,如果硬件类型嵌套在Option
或 的子类型中,则命名也有效Iterable
:
class Example3 extends Module {
val in = IO(Input(UInt(2.W)))
// val in = autoNameRecursively("in")(prefix("in")(IO(Input(UInt(2.W)))))
val out = IO(Output(UInt()))
// val out = autoNameRecursively("out")(prefix("out")(IO(Output(UInt(2.W)))))
def inXin() = in * in
val opt = Some(3.U + inXin())
// Note that the intermediate result of the inXin() is prefixed with `opt`:
// val opt = autoNameRecursively("opt")(prefix("opt")(Some(3.U + inXin())))
out := opt.get + 1.U
}
module Example3(
input clock,
input reset,
input [1:0] in,
output [3:0] out
);
wire [3:0] _opt_T = in * in; // @[naming.md 74:20]
wire [3:0] opt = 4'h3 + _opt_T; // @[naming.md 76:22]
assign out = opt + 4'h1; // @[naming.md 80:18]
endmodule
还有一个轻微的变体 ( autoNameRecursivelyProduct
) 用于使用 unapply 提供的名称命名硬件:
class UnapplyExample extends Module {
def mkIO() = (IO(Input(UInt(2.W))), IO(Output(UInt())))
val (in, out) = mkIO()
// val (in, out) = autoNameRecursivelyProduct(List(Some("in"), Some("out")))(mkIO())
out := in
}
module UnapplyExample(
input clock,
input reset,
input [1:0] in,
output [1:0] out
);
assign out = in; // @[naming.md 98:7]
endmodule
请注意,编译器插件在这些情况下不会插入前缀,因为前缀应该是什么是不明确的。鼓励需要前缀的用户提供如下所述的前缀。
前缀
如上所示,编译器插件会自动尝试为您的一些信号添加前缀。但是,您作为用户也可以添加自己的前缀。这尤其适用于 ECO 类型的修复,您需要向模块添加一些逻辑但又不想影响模块中的其他名称。
在以下示例中,我们在附加逻辑前面加上“ECO”,其中Example4
是 pre-ECO 和Example5
post-ECO:
class Example4 extends Module {
val in = IO(Input(UInt(2.W)))
val out = IO(Output(UInt()))
val add = in + in + in
out := add + 1.U
}
class Example5 extends Module {
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 Example4(
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 Example5(
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
另请注意,前缀是相互附加的(包括编译器插件生成的前缀):
class Example6 extends Module {
val in = IO(Input(UInt(2.W)))
val out = IO(Output(UInt()))
val add = prefix("foo") { in + in + in }
out := add
}
module Example6(
input clock,
input reset,
input [1:0] in,
output [1:0] out
);
wire [1:0] _add_foo_T_1 = in + in; // @[naming.md 147:32]
assign out = _add_foo_T_1 + in; // @[naming.md 147:37]
endmodule
有时您可能想要禁用前缀。如果您正在编写库函数并且不想要前缀行为,则可能会发生这种情况。在这种情况下,您可以使用noPrefix
对象:
class Example7 extends Module {
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
API。请注意,建议的名称仍将作为前缀(包括插件)。您始终可以使用该noPrefix
对象来剥离它。
class Example8 extends Module {
val in = IO(Input(UInt(2.W)))
val out = IO(Output(UInt()))
val add = (in + (in + in).suggestName("foo"))
out := add
}
module Example8(
input clock,
input reset,
input [1:0] in,
output [1:0] out
);
wire [1:0] add_foo = in + in; // @[naming.md 185:23]
assign out = in + add_foo; // @[naming.md 185:17]
endmodule
设置模块名称
如果要指定模块的名称(而不是模块的实例名称),您始终可以覆盖该desiredName
值。请注意,您可以通过模块的参数参数化名称。这是使您的模块名称更稳定的好方法,强烈建议您这样做。
class Example9(width: Int) extends Module {
override val desiredName = s"EXAMPLE9WITHWIDTH$width"
val in = IO(Input(UInt(width.W)))
val out = IO(Output(UInt()))
val add = (in + (in + in).suggestName("foo"))
out := add
}
module EXAMPLE9WITHWIDTH8(
input clock,
input reset,
input [7:0] in,
output [7:0] out
);
wire [7:0] add_foo = in + in; // @[naming.md 205:23]
assign out = in + add_foo; // @[naming.md 205:17]
endmodule
module EXAMPLE9WITHWIDTH1(
input clock,
input reset,
input in,
output out
);
wire add_foo = in + in; // @[naming.md 205:23]
assign out = in + add_foo; // @[naming.md 205:17]
endmodule
反射命名
无论编译器插件是否启用,在 Chisel 构造一个模块后,它都会尝试命名该模块的所有成员。这将命名作为模块类的字段的所有 val,但不会命名嵌套函数或范围中的任何 val。
如果插件成功命名一个信号,反射命名将什么都不做。我们计划在未来的 Chisel 版本中弃用所有反射命名,但保留它以允许插件命名是可选的(但推荐)。
例如,以下模块中的信号在嵌套范围内;插件成功命名它们,但反射命名不能:
class Example10 extends Module {
{
val in = IO(Input(UInt(3.W)))
val out = IO(Output(UInt()))
val add = in + in
out := add
}
}
@chiselName
不再推荐使用此宏,因为它的功能完全被编译器插件取代。随意从您的chisel设计中删除!