Chisel模块详解(四)——用函数实现轻量级模块
前面三篇文章我们学习了模块的实现语法、模块嵌套以及模块之间的连接方法,一般来说模块是Chisel中构造硬件描述的基本方法。但是Chisel中的模块和Verilog这类语言一样,定义模块会需要很多样板代码,又是从Module
拓展,又是定义IO
接口的,实例化和连接的时候又很费劲。不过Chisel中还是有简单的方法来定义轻量级模块的,那就是使用函数来实现。这一篇文章就讲讲如何用函数实现轻量级模块。
函数实现轻量级组合电路
Scala函数可以接受Chisel类型的参数并返回生成的硬件,举一个简单的例子,还是加法器,用Chisel的模块来实现是这样的:
class adder extends Module {
val io = IO(new Bundle {
val x = Input(UInt(32.W))
val y = Input(UInt(32.W))
val z = Output(UInt(32.W))
})
z := x + y
}
然后使用adder
模块的使用就用new
实例化,然后用Module
封装,再把它的输入输出和要使用adder
的信号连接起来,比如:
class testModule extends Module {
val io = IO(new Bundle {
val a = Input(UInt(32.W))
val b = Input(UInt(32.W))
val out = Output(UInt(32.W))
})
val adder = Module(new adder())
adder.io.x := io.a
adder.io.y := io.b
io.out := adder.io.z
}
生成的Verilog代码如下:
module adder(
input [31:0] io_x,
input [31:0] io_y,
output [31:0] io_z
);
assign io_z = io_x + io_y; // @[hello.scala 13:18]
endmodule
module test(
input clock,
input reset,
input [31:0] io_a,
input [31:0] io_b,
output [31:0] io_out
);
wire [31:0] adder_io_x; // @[hello.scala 22:23]
wire [31:0] adder_io_y; // @[hello.scala 22:23]
wire [31:0] adder_io_z; // @[hello.scala 22:23]
adder adder ( // @[hello.scala 22:23]
.io_x(adder_io_x),
.io_y(adder_io_y),
.io_z(adder_io_z)
);
assign io_out = adder_io_z; // @[hello.scala 25:12]
assign adder_io_x = io_a; // @[hello.scala 23:16]
assign adder_io_y = io_b; // @[hello.scala 24:16]
endmodule
其实是有点麻烦的,而且就这还整了个模块嵌套,其实我们可以用函数来生成一个adder
:
def adder (x: UInt, y: UInt) = {
x + y
}
这个函数的参数是两个Chisel的UInt
,返回值是x + y
,现在可以这么生成并使用加法器:
class testModule extends Module {
def adder (x: UInt, y: UInt) = {
x + y
}
val io = IO(new Bundle {
val a = Input(UInt(32.W))
val b = Input(UInt(32.W))
val out = Output(UInt(32.W))
})
val out = adder(io.a, io.b)
io.out := out
}
生成的Verilog代码如下:
module testModule(
input clock,
input reset,
input [31:0] io_a,
input [31:0] io_b,
output [31:0] io_out
);
assign io_out = io_a + io_b; // @[hello.scala 18:11]
endmodule
非常简洁,没有模块的嵌套,毕竟加法器这种组件还用个模块有点太奢侈的。
这就是轻量级模块,而adder
函数就是个硬件生成器。在展开的时候这个函数不会执行加法操作,而是创建了一个加法器的硬件实例。当然了,这里加法器的例子是刻意选取的简单例子,Chisel已经定义了加法器函数,也就是+(that:UInt)
运算符。
函数实现轻量级有状态电路
函数作为轻量级的硬件生成器,还可以包含状态,比如寄存器。下面的例子会返回一个单周期延迟模块(也就是寄存器):
def delay(x : UInt) = RegNext(x)
如果函数体只有一行的话,我们可以省略大括号,然后和函数写到一行里面。
我们现在可以通过以这个函数为参数调用这个函数构造一个二周期延迟模块:
class testModule extends Module {
def delay(x : UInt) = RegNext(x)
val io = IO(new Bundle {
val delIn = Input(UInt(32.W))
val delOut = Output(UInt(32.W))
})
// 调用两次delay
io.delOut := delay(delay(io.delIn))
}
生成的Verilog代码如下:
module testModule(
input clock,
input reset,
input [31:0] io_delIn,
output [31:0] io_delOut
);
reg [31:0] io_delOut_REG; // @[hello.scala 17:34]
reg [31:0] io_delOut_REG_1; // @[hello.scala 17:34]
assign io_delOut = io_delOut_REG_1; // @[hello.scala 24:15]
always @(posedge clock) begin
io_delOut_REG <= io_delIn; // @[hello.scala 17:34]
io_delOut_REG_1 <= io_delOut_REG; // @[hello.scala 17:34]
end
endmodule
可以看到,构造的模块中有两个寄存器,输入会在另两个周期后变成输出。不过这个例子跟加法器的例子一样的,RegNext()
也是Chisel中已经有了的函数,这里直接调用RegNext()
替代delay
是一样的。
上面的两个例子中,函数都是在Module
内部定义的,但是通常这种模块都会在不同的模块中用到,所以更好的办法是把这类实用函数都放到一个Scala对象里面,不管在哪个模块都可以调用。
结语
这一篇文章讲述的函数实现轻量级模块内容很简单,用好可以帮我们节约很多时间,在Scala对象中实现所有实用函数可以方便所有模块调用,大幅度提升编码效率。不过首先还是用好Chisel提供了的函数,避免重复造轮子,也方便Chisel编译器优化生成的硬件电路。除了Chisel中我们写的模块,我们可能还需要在我们的工程中使用现有的IP,而这些IP通常是用Verilog来写的,我们该如何使用这些IP呢?下一篇文章就详细说说。