前一篇见此:
chisel快速入门(二)_沧海一升的博客-CSDN博客简单介绍了chisel,使硬件开发者能快速上手chisel。https://blog.csdn.net/qq_21842097/article/details/121418806
十四、模块的功能创建
制造用于模块构造的功能接口也是有用的。例如,我们可以构建一个构造函数,它将多路复用器输入作为参数,并返回多路复用器输出:
object Mux2 {def apply (sel: UInt, in0: UInt, in1: UInt) = {val m = new Mux2() m.io.in0 := in0 m.io.in1 := in1 m.io.sel := sel m.io.out}
}
其中对象Mux2在Mux2模块类中创建一个Scala单例对象,并且apply定义了创建Mux2实例的方法。有了这个Mux2创建功能,Mux4的规格现在明显更简单。
class Mux4 extends Module { val io = new Bundle {val in0 = UInt(INPUT, 1)val in1 = UInt(INPUT, 1) val in2 = UInt(INPUT, 1) val in3 = UInt(INPUT, 1) val sel = UInt(INPUT, 2) val out = UInt(OUTPUT, 1)}io.out := Mux2(io.sel(1), Mux2(io.sel(0), io.in0, io.in1), Mux2(io.sel(0), io.in2, io.in3))
}
选择输入非常有用,以至于 Chisel 将其内置并称之为 Mux。 然而,与上面定义的 Mux2 不同,内置版本允许 in0 和 in1 上的任何数据类型,只要它们有一个共同的超类。
Chisel提供MuxCase,其本质上是一个n-way Mux。
MuxCase(default, Array(c1 -> a, c2 -> b, ...))
十五、多态性和参数化
Scala是一种强类型语言,使用参数化类型来指定通用函数和类。 在本节中,我们展示了Chisel用户如何使用参数化类来定义自己的可重用函数和类。
1、参数化函数
前面我们在Bool上定义了Mux2,但现在我们展示如何定义一个通用的多路复用器功能。我们使用一个布尔条件和con和alt参数(对应于then和else表达式)来定义一个T类型的函数:
def Mux[T <: Bits](c: Bool, con: T, alt: T): T { ... }
其中T需要是Bits的子类。Scala确保在Mux的每个使用中,它可以找到实际的con和alt参数类型的公共超类,否则会导致Scala编译类型错误。
2、参数化类
与参数化函数一样,我们也可以参数化类,使它们可重用程度更高。例如,我们可以将Filter类概括为可以使用任何类型的链接。
我们可以通过参数化FilterIO类和定义构造函数采取零参数类型构造函数来做到这点,如下所示:
class FilterIO[T <: Data](type: T) extends Bundle { val x = type.asInput.flipval y = type.asOutput
}
我们现在可以通过定义一个模块类来定义Filter,该模块类也接收一个链接类型构造函数参数,并将其传递给FilterIO接口构造器:
class Filter[T <: Data](type: T) extends Module { val io = new FilterIO(type)...
}
另一个例子,通用FIFO可以这样定义,并使用如下:
class DataBundle extends Bundle { val A = UInt(width = 32)val B = UInt(width = 32)
}
object FifoDemo {def apply () = new Fifo(new DataBundle, 32)
}class Fifo[T <: Data] (type: T, n: Int) extends Module {val io = new Bundle {val enq_val = Bool(INPUT) val enq_rdy = Bool(OUTPUT) val deq_val = Bool(OUTPUT) val deq_rdy = Bool(INPUT) val enq_dat = type.asInput val deq_dat = type.asOutput}val enq_ptr = Reg(init = UInt(0, sizeof(n)))val deq_ptr = Reg(init = UInt(0, sizeof(n)))val is_full = Reg(init = Bool(false))val do_enq = io.enq_rdy && io.enq_valval do_deq = io.enq_rdy && io.deq_val val is_empty = !is_full && (enq_ptr === deq_ptr)val deq_ptr_inc = deq_ptr + UInt(1)val enq_ptr_inc = enq_ptr + UInt(1)val is_full_next = Mux(do_enq && ~do_deq && (enq_ptr_inc === deq_ptr), Bool(true), Mux(do_deq && is_full, Bool(false), is_full)) enq_ptr := Mux(do_enq, enq_ptr_inc, enq_ptr) deq_ptr := Mux(do_deq, deq_ptr_inc, deq_ptr) is_full := is_full_nextval ram = Mem(n) when (do_enq) {ram(enq_ptr) := io.enq_dat }io.enq_rdy := !is_full io.deq_val := !is_empty ram(deq_ptr) <> io.deq_dat
}
对FIFO定义通用解耦接口,可以简化IO:
class DecoupledIO[T <: Data](data: T) extends Bundle {val ready = Bool(INPUT)val valid = Bool(OUTPUT)val bits = data.clone.asOutput
}class DecoupledDemo
extends DecoupledIO()( new DataBundle )class Fifo[T <: Data] (data: T, n: Int) extends Module {val io = new Bundle {val enq = new DecoupledIO( data ).flip() val deq = new DecoupledIO( data )}...
}
十六、多时钟域
1、创建时钟域
为了使用多个时钟域,用户必须创建多个时钟。 在Chisel中,时钟是使用复位信号参数创建的第一级节点,定义如下:
class Clock (reset: Bool) extends Node { def reset: Bool // returns reset pin
}
在Chisel中有一个内置的隐式时钟,状态元素默认使用:
var implicitClock = new Clock( implicitReset )
状态元素和模块的时钟可以使用名为clock的附加命名参数来定义:
Reg(... clock: Clock = implicitClock)
Mem(... clock: Clock = implicitClock)
Module(... clock: Clock = implicitClock)
2、跨时钟域
有两种方式可以定义电路在时钟域之间发送数据。第一种也是最原始的方式就是使用由两个寄存器组成的同步器电路,如下所示:
// signalA is in clock domain clockA,
// want a version in clockB as signalB
val s1 = Reg(init = UInt(0), clock = clockB)
val s2 = Reg(init = UInt(0), clock = clockB)
s1 := signalA
s2 := s1;
signalB := s2
由于亚稳性问题,该技术只限于在域之间传递一位数据。
在域之间发送数据的第二种和更一般的方式是通过使用异步fifo:
class AsyncFifo[T<:Data](gen: T, entries: Int, enq_clk: Clock, deq_clock:Clock) extends Module
当通过指定标准fifo参数和两个时钟,然后使用标准解耦ready/valid信号从时钟域clockA到clockB获取一个版本的signalA时:
val fifo =
new AsyncFifo(Uint(width = 32), 2, clockA, clockB)
fifo.io.enq.bits := signalA
signalB := fifo.io.deq.bits
fifo.io.enq.valid := condA
fifo.io.deq.ready := condB
3、后端多时钟处理
每个 Chisel 后端都需要用户以后端特定的方式设置和控制多个时钟。 为了展示如何驱动多时钟设计,我们以具有两个模块的硬件为例进行说明,该例子使用 AsyncFifo 与不同时钟的每个模块进行通信:fastClock 和 slowClock。
在Verilog中:
- Chisel为每个时钟/复位创建一个新端口,
- Chisel将所有的时钟连到顶层模块
- 用户必须要为每个时钟i创建一个always块时钟驱动
module emulator;reg fastClock = 0, slowClock = 0, resetFast = 1, resetSlow = 1; wire [31:0] add, mul, test;always #2 fastClock = ~fastClock;always #4 slowClock = ~slowClock; initial begin# 8resetFast = 0; resetSlow = 0; #400$finish;
end
ClkDomainTest dut (.fastClock(fastClock), .slowClock(slowClock), .io_resetFast(resetFast),.io_resetSlow(resetSlow),.io_add(add), .io_mul(mul), .io_test(test));
endmodule