在数字电路中免不了用到多时钟域设计,尤其是设计异步FIFO这样的同步元件。
- 在Verilog里,多时钟域的设计很简单,只需声明多个时钟端口,然后不同的always语句块根据需要选择不同的时钟作为敏感变量即可。
- 在Chisel里,则相对复杂一些,因为这与Scala的变量作用域相关,而且时序元件在编译时都是自动地隐式跟随当前时钟域。
本章将介绍多时钟域设计的语法,这其实很简单。
一、没有隐式端口的模块继承自Module的模块类会获得隐式的全局时钟与同步复位信号,即使在设计中用不上它们也没关系。如果读者确实不喜欢这两个隐式端口,则可以选择继承自RawModule
,这样在转换成Verilog时就没有隐式端口。它在chisel3包里,也是UserModule类的别名
。
这样的模块一般用于纯组合逻辑。在类内顶层不能出现使用时钟的相关操作(这里之所以说顶层,是因为还会存在自定义时钟域和复位域),比如定义寄存器(因为寄存器是时序元件,需要用到时钟和复位信号),否则会报错没有隐式端口。例如:
// module.scala
package test
import chisel3._
import chisel3.experimental._
class MyModule extends RawModule {
val io = IO(new Bundle {
val a = Input(UInt(4.W))
val b = Input(UInt(4.W))
val c = Output(UInt(4.W))
})
io.c := io.a & io.b
}
object ModuleGen extends App {
chisel3.Driver.execute(args, () => new MyModule)
}
它生成的Verilog代码为:
// MyModule.v
module MyModule(
input [3:0] io_a,
input [3:0] io_b,
output [3:0] io_c
);
assign io_c = io_a & io_b; // @[module.scala 13:8]
endmodule
RawModule也可以包含时序逻辑,但要使用多时钟域语法。
二、定义一个时钟域和复位域chisel3包里有一个单例对象withClockAndReset
(这点和原文不同,因为该单例对象已经移到了chisel3包里面),其apply方法定义如下:
def apply[T](clock: Clock, reset: Reset)(block: ⇒ T): T
该方法的作用就是创建一个新的时钟和复位域,作用范围仅限于它的传名参数的内部。
class MultiClockModule extends Module {
val io = IO(new Bundle {
//注意不要忘记定义自定义的时钟和复位信号端口,
//以前不定义,是因为隐式的时钟和复位信号会自动添加这两个端口
val clockB = Input(Clock())
val resetB = Input(Bool())
val stuff = Input(Bool())
})
// 这个寄存器跟随当前模块的隐式全局时钟clock
val regClock1 = RegNext(io.stuff)
withClockAndReset(io.clockB, io.resetB) {
// 在该花括号内,所有时序元件都跟随时钟io.clockB
// 所有寄存器的复位信号都是io.resetB
// 这个寄存器跟随io.clockB
val regClockB = RegNext(io.stuff)
// 还可以例化其它模块
val m = Module(new ChildModule)
}
// 这个寄存器跟随当前模块的隐式全局时钟clock
val regClock2 = RegNext(io.stuff)
}
注意不要忘记在IO中定义自定义的时钟域和复位域要用的时钟和复位信号的端口;使用隐式时钟和复位域时不用定义,是因为隐式的时钟和复位信号的端口会被自动添加。
因为第二个参数列表只有一个传名参数,所以可以把圆括号写成花括号,这样还有自动的分号推断。再加上传名参数的特性,尽管需要一个无参函数,但是可以省略书写“() =>”。所以,
withClockAndReset(io.clockB, io.resetB) {
sentence1
sentence2
...
sentenceN
}
实际上相当于:
withClockAndReset(io.clockB, io.resetB)( () => (sentence1; sentence2; ...; sentenceN) )
这结合了Scala的柯里化、传名参数和单参数列表的语法特性,让DSL语言的自定义方法看上去就跟内建的while、for、if等结构一样自然,所以Scala很适合构建DSL语言。
读者再仔细看一看apply方法的定义,它的第二个参数是一个函数,同时该函数的返回结果也是整个apply方法的返回结果。也就是说,独立时钟域的定义里,最后一个表达式的结果会被当作函数的返回结果。可以用一个变量来引用这个返回结果,这样在独立时钟域的定义外也能使用。例如引用最后返回的模块:
class MultiClockModule extends Module {
val io = IO(new Bundle {
val clockB = Input(Clock())
val resetB = Input(Bool())
val stuff = Input(Bool())
})
val clockB_child = withClockAndReset(io.clockB, io.resetB) {
Module(new ChildModule)
}
clockB_child.io.in := io.stuff
}
如果传名参数全都是定义,最后没有表达式用于返回,那么apply的返回结果类型自然就是Unit。此时,外部不能访问独立时钟域里的任何内容。例如把上个例子改成如下代码:
class MultiClockModule extends Module {
val io = IO(new Bundle {
val clockB = Input(Clock())
val resetB = Input(Bool())
val stuff = Input(Bool())
})
val clockB_child = withClockAndReset(io.clockB, io.resetB) {
val m = Module(new ChildModule)
}
clockB_child.m.io.in := io.stuff
}
现在,被例化的模块不是作为返回结果,而是变成了变量m的引用对象,故而传名参数是只有定义、没有有用的返回值的空函数。如果编译这个模块,就会得到“没有相关成员”的错误信息:
[error] /home/esperanto/chisel-template/src/main/scala/module.scala:42:16:
value m is not a member of Unit
[error] clockB_child.m.io.in := io.stuff
[error] ^
如果独立时钟域有多个变量要与外部交互,则应该在模块内部的顶层定义全局的线网,让所有时钟域都能访问。
注1:
注意,是定义在模块内部的顶层,不能定义在某一个时钟域内,否则外顶层和其他时钟域都无法访问
除了单例对象withClockAndReset
,还有单例对象withClock和withReset
,分别用于构建只有独立时钟和只有独立复位信号的作用域,三者的语法是一样的,具体可以参考下例。
下面再举一个例子来引出一些细节:
package grammer
import chisel3._
class ChildModule extends Module {
val io = IO(new Bundle{
val in = Input(Bool())
val clockChild = Input(Clock())
val out = Output(Bool())
})
withClock(io.clockChild){
//该寄存器跟随时钟io.clockChild,隐式复位信号reset
val regclock = RegNext(io.in,0.U)
io.out := regclock
}
}
class MultiClockTester extends Module {
val io = IO(new Bundle {
//注意不要忘记定义自定义的时钟和复位信号端口,
//以前不定义,是因为隐式的时钟和复位信号会自动添加这两个端口
val clockA = Input(Clock())
val resetA = Input(Bool())
val clockChild = Input(Clock())
val resetB = Input(Bool())
val stuff_in = Input(Bool())
val stuff_out = Output(Bool())
val outregClock = Output(Bool())
val outregClockA = Output(Bool())
val outregClockB = Output(Bool())
})
// 这个寄存器跟随当前模块的隐式全局时钟clock
val regClock = RegNext(io.stuff_in,0.U)
val clockA_child = withClockAndReset(io.clockA,io.resetA.asAsyncReset()) {
// 在该花括号内,所有时序元件都跟随时钟io.clockA
// 所有寄存器的复位信号都是io.resetA
// 这个寄存器跟随io.clockA
val regClockA = RegNext(io.stuff_in,0.U)
regClock := regClockA
io.outregClockA := regClockA
Module(new ChildModule)
}
clockA_child.io.clockChild := io.clockChild
clockA_child.io.in := io.stuff_in
io.stuff_out := clockA_child.io.out
withReset(io.resetB) {
// 在该花括号内,所有时序元件都跟随时钟隐式时钟clock
// 所有寄存器的复位信号都是io.resetB
// 这个寄存器跟随clock
val regClockB = RegNext(io.stuff_in,0.U)
io.outregClock := regClock
io.outregClockB := regClockB
}
}
object MultiClockTester extends App {
(new chisel3.stage.ChiselStage).emitVerilog(new MultiClockTester(), Array("--target-dir", "generated"))
}
生成的部分verilog代码如下:
module ChildModule(
input reset,
input io_in,
input io_clockChild,
output io_out
);
`ifdef RANDOMIZE_REG_INIT
reg [31:0] _RAND_0;
`endif // RANDOMIZE_REG_INIT
reg REG; // @[MultiClockTester.scala 12:27]
assign io_out = REG; // @[MultiClockTester.scala 13:12]
always @(posedge io_clockChild or posedge reset) begin
if (reset) begin
REG
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?