您当前的位置: 首页 > 

耐心的小黑

暂无认证

  • 1浏览

    0关注

    323博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

chisel多时钟域设计(注释)

耐心的小黑 发布时间:2021-06-21 19:10:14 ,浏览量:1

在数字电路中免不了用到多时钟域设计,尤其是设计异步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             
关注
打赏
1640088279
查看更多评论
0.0455s