Arbiter
和Queue
都使用了ready-valid
接口,该类型的端口在单一数据信号的基础上又添加了ready和valid信号以使用ready-valid
握手协议。它包含3个信号:
- ready:高有效时表示数据接收者
consumer
已经准备好接收信号,由consumer驱动。 - valid:高有效时表示数据生产者
producer
已经准备好待发送的数据了,由producer驱动。 - bits:是要在producer与consumer之间传输的数据。
需要注意的是,valid和ready信号之间不能存在组合逻辑关系,valid信号应该只依赖于此时的源数据是否有效,ready信号应该只依赖于此时的数据接收者是否准备好接收数据了。当在某个时钟周期,valid和ready同时有效时,数据被视为传输。
创建ready-valid接口很简单,使用单例对象Decoupled即可创建,有以下两种形式:
-
Decoupled(...)
:可以传入任意的数据类型,然后返回一个ready-valid接口,此时ready是input信号,valid和bits都是output信号。因此它是属于数据生产者producer的端口。 -
Flipped(Decoupled(...))
:Flipped()会将ready-valid接口的信号方向进行取反,因此此时ready是output信号,valid和bits都是input信号。因此它是属于数据接收者consumer的端口。
数据接收者和发送者都是相对的,一定要根据具体的情况正确设置信号方向。
Chisel内建了两种仲裁器,一种是优先仲裁器,另一种是循环仲裁器。
- 优先仲裁器的输入通道的优先级是固定的,每次都是选择多个有效通道中优先级最高的。
- 而循环仲裁器每次都从不同的起点开始仲裁,采用轮询方式查看各个通道是否有请求,优先选择先查到的有效通道。由于起点是依次变化的,所以每个通道总体来说具有相同的优先级。
第一种仲裁器优先仲裁器Arbiter
在chisel3.util包下面,只定义了Arbiter类,没有单例对象,所以每次都需要通过new来创建Arbiter对象。
创建Arbiter对象的方式如下所示:
new Arbiter(gen: T, n: Int)
需要提供两个参数,gen是传输的数据的类型,n是待仲裁对象的个数,也即数据发送者producer的个数。数据接收者consumer的个数为默认为1。
Arbiter内部使用ArbiterIO
定义端口,而ArbiterIO内部又使用Decoupled()
创建最终所需的ready-valid接口,定义如下:
class ArbiterIO[T
c.io.in(0).valid.poke(false.B)
c.io.in(1).valid.poke(false.B)
c.io.out.ready.poke(false.B)
println(s"Start:")
println(s"\tin(0).ready=${c.io.in(0).ready.peek().litValue}, in(1).ready=${c.io.in(1).ready.peek().litValue}")
println(s"\tout.valid=${c.io.out.valid.peek().litValue}, out.bits=${c.io.out.bits.peek().litValue}")
c.io.in(1).valid.poke(true.B) // Valid input 1
c.io.in(1).bits.poke(42.U)
c.io.out.ready.poke(true.B)
// What do you think the output will be?
println(s"valid input 1:")
println(s"\tin(0).ready=${c.io.in(0).ready.peek().litValue}, in(1).ready=${c.io.in(1).ready.peek().litValue}")
println(s"\tout.valid=${c.io.out.valid.peek().litValue}, out.bits=${c.io.out.bits.peek().litValue}")
c.io.in(0).valid.poke(true.B) // Valid inputs 0 and 1
c.io.in(0).bits.poke(43.U)
// What do you think the output will be? Which inputs will be ready?
println(s"valid inputs 0 and 1:")
println(s"\tin(0).ready=${c.io.in(0).ready.peek().litValue}, in(1).ready=${c.io.in(1).ready.peek().litValue}")
println(s"\tout.valid=${c.io.out.valid.peek().litValue}, out.bits=${c.io.out.bits.peek().litValue}")
c.io.in(1).valid.poke(false.B) // Valid input 0
// What do you think the output will be?
println(s"valid input 0:")
println(s"\tin(0).ready=${c.io.in(0).ready.peek().litValue}, in(1).ready=${c.io.in(1).ready.peek().litValue}")
println(s"\tout.valid=${c.io.out.valid.peek().litValue}, out.bits=${c.io.out.bits.peek().litValue}")
}
Elaborating design… Done elaborating. Start: in(0).ready=0, in(1).ready=0 out.valid=0, out.bits=0 valid input 1: in(0).ready=1, in(1).ready=1 out.valid=1, out.bits=42 valid inputs 0 and 1: in(0).ready=1, in(1).ready=0 out.valid=1, out.bits=43 valid input 0: in(0).ready=1, in(1).ready=0 out.valid=1, out.bits=43 test Helper_Anon Success: 0 tests passed in 2 cycles in 0.077434 seconds 25.83 Hz
没有什么需要特别说明的,因为它就是一个组合逻辑的模块。
第二种仲裁器循环仲裁器RRArbiter
也在chisel3.util包下面,并且只定义了RRArbiter类,没有单例对象,所以每次都需要通过new来创建RRArbiter对象。它的创建与调用方式和Arbiter是一样的,只是内部实现的仲裁逻辑不同。
Chisel内建了队列Queue
,它会创建一个使用ready-valid接口 的FIFO,在chisel3.util包下面既定义了Queue类,也定义了其单例对象,所以有两种创建Queue对象的方式。
Queue内部使用QueueIO
定义端口,QueueIO最终仍然是使用Decoupled()
创建所需的ready-valid接口,定义如下:
class QueueIO[T
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【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脚手架写一个简单的页面?