Chisel模块详解(一)——初识Chisel模块(Module)并实现一个计数器
比较大的数字电路设计都会构造为一组组件,而且这些组件是有层级地组织起来的。每个组件都有自己输入输出线网的接口,通常叫作端口(port),跟集成电路(IC)中的输入输出引脚是差不多东西。组件之间可以通过线网连接输入输出而连接到一起,组件内部也可以包含子组件来构造有层级的数字电路。对于有层级的组件,最外层的组件是直接和芯片上的物理针脚连接的,被称作为顶层组件(Top-Level Component)。
Chisel模块详解这一部分会讲解组件在Chisel中是怎么描述的、又是如何整合到一起的,还提供了一些简单的组件的例子。不过这一部分涉及到的模块还都比较小,比如一个简单的加法器,只是用来展示定义、实例化和连接组件的基本原理。实际编写Chisel代码的时候,要写的模块肯定代码量大管饱,不像加法器一样就那么一两行。这一篇文章首先介绍Chisel的基本组件,也就是模块(Module)。
利用Chisel中的Module实现一个计数器
Chisel中将硬件组件叫作模块(Module),每个模块都是从Module
类拓展来的,包含一个用于定义接口的io
字段。这个接口通过Bundle
定义,然后通过调用IO()
封装起来。Bundle
前面讲过,它可以包含多个字段,这里就用于表示模块所有的输入输出接口。接口的方向通过调用Input()
或Output()
封装一个字段为输入或输出来确定,而这个输入输出是相对于这个模块自身来说的。
我们先举一个例子,一个八位无符号数的加法器模块,下面是加法器模块的示意图:
可以看到,这个加法器模块有两个输入(a
和b
),还有一个输出y
。下面的代码给出了Chisel中这个加法器的定义:
class Adder extends Module {
val io = IO(new Bundle {
val a = Input(UInt(8.W))
val b = Input(UInt(8.W))
val y = Output(UInt(8.W))
})
io.y := io.a + io.b
}
模块中对输入输出接口的访问通过.
运算符完成,比如io.a
,因为这些输入输出是io
这个Bundle
的成员。
再来一个简单的例子,是个八位的无符号整数寄存器,示意图如下:
输入只有一个d
,输出是q
,d
为下个时钟周期上升沿存到寄存器的值,q
是当前寄存器存放的值。在Chisel中的实现如下:
class Register extends Module {
val io = IO(new Bundle {
val d = Input(UInt(8.W))
val q = Output(UInt(8.W))
})
val reg = RegInit(0.U)
io.q := reg
reg := io.d
}
有了一个加法器,又有了一个寄存器,现在我们可以用它们构造一个计数器,计数从0到9,然后再从0开始计数,示意图如下:
看起来稍微有点复杂了,但稍微观察就可以发现,这个计数器就是由一个加法器、一个Mux和一个寄存器组成的。加法器用于累加1,Mux用于选择是否清0重置计数器,即选择输出加法器的计算结果还是输出0,寄存器用于保存当前计数器的值。那么在Chisel上实现如下:
class Count10 extends Module {
val io = IO(new Bundle {
val dout = Output(UInt(8.W))
})
// 利用写好的两个模块
val add = Module(new Adder())
val reg = Module(new Register())
// 寄存器存放的为当前的计数
val count = reg.io.q
// 当前计数和1.U作为加法器的输入
add.io.a := 1.U
add.io.b := count
val result = add.io.y
// 如果累加达到了10.U,则计数器清零
val next = Mux(result === 10.U, 0.U, result)
reg.io.d := next
io.dout := count
}
可以看到代码中,我们使用new
关键字实例化了两个模块,然后封装为一个Module
并给了它命名,即add
和reg
。加法器的输入是1.U
和count
,输出命名为result
,如果result
达到了10.u
,就代表应该清零了,输出也应该是0,所以我们用Mux根据result
的值是否为10.U
来选择输出0.U
还是result
,也可以根据count
的值是否为9.U
来判断。最后我们把Mux的输出作为寄存器的输入,寄存器的输出作为Count10
模块的输出io.dout
。如此,一个Chisel计数器就实现了。
结语
这一篇文章以加法器、寄存器和计数器的例子说明了Chisel中模块的用法,不过只是给了个初步的印象。这一部分的后面几篇文章会讲解更进阶的模块的用法,比如嵌套的模块、Bulk连接等,并用他们实现一些稍微丰富的模块。最后还会专门用一篇文章来讲Chisel模块如何连接外部的模块,比如一个Verilog
模块,这在使用IP的时候很有用,敬请期待。