Chisel 手册(中文part2)
作者:Jonathan Bachrach, Huy Vo, Krste Asanović; EECS Department, UC Berkeley
译者:智能物联(CSDN)
6 Updateables
当描述wire和state节点的运作时,我们通常为输出端口指定一系列的条件更新,然后用若干独立语句把这些更新散播出去。举例来说,Data节点的输出可以立刻引用,但是输入可以延后设置。Updateable表示一个条件更新节点,它累积针对节点的路径,然后生成mux并对这些电路的路径进行组合逻辑运算。
abstract class Updateable extends Node { // conditional reads def reads: Queue[(Bool, UInt)] // conditional writes def writes: Queue[(Bool, UInt, Node)] // gen mux integrating all conditional writes def genMuxes(default: Node) override def := (x: Node): this.type
}
Chisel提供条件更新规则,用when语句支持这类时序逻辑描述:
object when { def apply(cond: Bool)(block: => Unit): when
} class when (prevCond: Bool) { def elsewhen (cond: Bool)(block: => Unit): when def otherwise (block: => Unit): Unit
}
when语句使用动态作用域scope来操纵全局条件栈,因此,when语句生成新的条件,将在函数调用路径内一直起作用。举个例子:
def updateWhen (c: Bool, d: Data) = when (c) { r := d }
when (a) { updateWhen(b, x)
}
与下列相同:
when (a) { when (b) { r := x }
}
Chisel为其他常用类型的条件更新提供一些语法糖:
def unless(c: Bool)(block: => Unit) = when (!c) { block )
还有
def otherwise(block: => Unit) = when (Bool(true)) { block }
我们介绍了switch语句,用于根据一系列普通key的比较结果进行条件更新:
def switch(c: UInt)(block: => Unit): Unit def is(v: Bool)(block: => Unit)
7 Forward Declarations
纯组合电路不允许出现节点的闭环。如果发现闭环,Chisel会报错。因为不存在闭环,所以可以生成单方向的合法的组合逻辑电路,通过添加新的节点,输入取自那些已定义的节点。时序电路具有节点之间的反馈,所以有时候必须在定义生成某个节点之前声明这个输出。因为Scala顺序执行程序语句,我们一般让数据节点作为wire立即引用,但是它们的输入延后设置。举例来说,在一个简单的CPU中,我们需要预先声明pcPlus4和brTarget这两个wires,以便后续引用:
val pcPlus4 = UInt()
val brTarget = UInt()
val pcNext = Mux(pcSel, brTarget, pcPlus4)
val pcReg = RegUpdate(pcNext)
pcPlus4 := pcReg + UInt(4)
...
brTarget := addOut
连线运算符 := 用于连接pcReg与addOut。所有的赋值完成,所有的电路都计算以后,如果还有前向声明还没有被赋值的话,系统将会报告错误。
8 Regs
Chisel支持的最简单的状态单元是正沿触发的寄存器,如下所示:
object Reg { def apply[T <: Data] (data: T, next: T = null, init: T = null): T
} object RegNext { def apply[T <: Data] (next: T, init: T = null): T
} object RegInit { def apply[T <: Data] (init: T): T
} class Reg extends Updateable
创建方法如下所示:
val r1 = RegUpdate(io.in)
val r2 = RegReset(UInt(1, 8))
val r3 = RegUpdate(io.in, UInt(1))
val r4 = Reg(UInt(width = 8))
复位值resetVal是隐含的reset信号为真时候所取的reg值。
9 Mems
Chisel通过Mem语句支持RAM。写入Mem是正沿触发,读取是组合逻辑或者正沿触发。
object Mem { def apply[T <: Data](depth: Int, gen: => T, seqRead: Boolean = false): Mem
} class Mem[T <: Data](gen: () => T, depth: Int, seqRead: Boolean = false) extends Updateable { def apply(idx: UInt): T
}
Mems内部的Ports被赋值UInt的索引 。一个具有一个写端口和两个组合逻辑读端口的32-entry的寄存器组描述如下:
val rf = Mem(32, UInt(width = 64))
when (wen) { rf(waddr) := wdata }
val dout1 = rf(waddr1)
val dout2 = rf(waddr2)
如果设置可选参数seqRead,当Reg被赋予Mem的输出,Chisel会生成时序读取端口。一个单口读,单口写的SRAM可以描述如下:
val ram1r1w = Mem(1024, UInt(width = 32), seqRead = true)
val dout = Reg(UInt())
when (wen) { ram1r1w(waddr) := wdata }
when (ren) { dout := ram1r1w(raddr) }
在when语句链条中,如果读和写的条件互斥,将生成单端口的SRAM。
val ram1p = Mem(1024, UInt(width = 32), seqRead = true)
val dout = Reg(UInt())
when (wen) { ram1p(waddr) := wdata }
.elsewhen (ren) { dout := ram1p(raddr) }
如果同一个Mem地址在同一个clock边沿同步读写,或者如果sequential read使能被清除,读数据是用户自定义的
Mem 也支持写入mask,以便部分字节写入。
val ram = Mem(256, UInt(width = 32))
when (wen) { ram.write(waddr, wdata, wmask) }
10 Ports
端口继承自Data节点,用于硬件模块的接口。端口是原始Data对象的有方向版本。端口方向定义如下:
trait PortDir
object INPUT extends PortDir
object OUTPUT extends PortDir
复合端口可以递归创建,使用vec或者bundle组合简单的子端口。
11 Modules
在Chisel中,模块Module与Verilog中的模块非常相似,模块定义了一个被生成电路的层次结构。模块的命名空间可以被下游的工具来访问,用于调试和物理布局。一个用户自定义模块被定义成一个class:
- 继承自Module,
- 包含一个接口Bundle,保存于一个io,
- 在构造器中用连线连接子电路。
用户通过module的子类来写自己的模块:
abstract class Module { val io: Bundle var name: String = "" def compileV: Unit def compileC: Unit
}
定义它们自己的io。下例定义了一个双输入Mux的模块:
class Mux2 extends Module { val io = new Bundle{ val sel = Bool(INPUT) val in0 = Bool(INPUT) val in1 = Bool(INPUT) val out = Bool(OUTPUT) } io.out := (io.sel & io.in1) | (~io.sel & io.in0)
}
:= 赋值运算符,用于module内部,是Chisel的一个特殊运算符,连接左侧的输入到右侧的输出。典型地用于连接输出端口和它们的定义。
<> 运算符成捆连接兄弟模块之间的相反的接口,或者父子模块之间的相同的接口。成捆连接使用路径名匹配方式连接叶端口。连接只在port非无效的时候进行,允许用户重复成捆连接那些只有部分使用的接口。所有的连接完成以后电路将被优化,Chisel 如果发现端口不止一个连接,将会发出警告。
模块内部的节点和子模块的命名,由C++或者Verilog后端负责,取自模块的域名,使用Scala内审机制。使用函数setName()也可以设置节点和子模块的名字。