chisel学习4 组合构建块 Combinational Building Blocks

在本章中,我们将探索各种组合电路,这些电路是我们可以用来构建更复杂系统的基本构建模块。原则上,所有的组合电路都可以用布尔方程来描述。然而,更常见的是,表格形式的描述更有效。我们让合成工具提取和最小化布尔方程。最好用表格形式描述的两个基本电路是解码器和编码器。

组合电路

在描述一些标准的组合构建块之前,我们将探讨如何在Chisel中表达组合电路。最简单的形式是一个布尔表达式,它可以被分配一个名称:

val e = (a & b) | c

布尔表达式通过将其赋值给Scala值而被赋予名称(e)。该表达式可以在其他表达式中重用:

val f = ~e

这样的表达式被认为是固定的。使用=对e进行重新赋值会导致Scala编译器错误:reassignment to val (重新分配给val)。可以使用Chisel操作符:=进行尝试

e := c & b

导致运行时异常:无法重新指定为只读。

Chisel还支持用条件更新描述组合电路。这样的回路被声明为Wire。然后使用条件运算(如when)来描述电路的逻辑。下面的代码声明了一个UInt类型的Wire w,并指定了默认值0。when块接受一个Chisel Bool,如果cond为true,则将3重新分配给w。
在这里插入图片描述

val w = Wire(UInt())

w := 0.U
when (cond){
	w := 3.U
}

该电路的逻辑是一个多路复用器,其中两个输入是常数0和3,条件是选择信号。请记住,我们描述的是硬件电路,而不是有条件执行的软件程序。

Chisel条件构造when也有else的形式,则称为 . otherwise。在任何条件下赋值,我们都可以省略默认赋值:

val w = Wire(UInt())

when (cond){
  w := 1.U
  } .otherwise {
  w := 2.U
  }

Chisel还支持一个条件链(一个if/elseif/else链)和.elsewhen:

val w = Wire(UInt())
when (cond){
	w := 1.U
	} .elsewhen(cond2){
	w := 2.U
	} .otherwise{
	w := 3.U
	}

这个when、.elsewhen和.otherwise的链构成了一个多路复用器链。图5.1显示了这个多路复用器链。该链引入优先级,即,当cond为真时,不评估其它条件。

请注意’.'in .elsewhen在Scala中需要链接方法时。那些.elsewhen分支可以是任意长的。然而,如果条件链依赖于单个信号,则最好使用switch语句,该语句将在下面的小节中与解码器电路一起介绍。

对于更复杂的组合电路,为“导线”指定默认值可能更实用。默认赋值可以与WireDefault的关联声明结合使用。

val w = WireDefault(0.U)

when (cond) {
	w := 3.U
	}

有人可能会问,当Scala有if、else if和else时,为什么要使用when、.elsewhen和.otherwise.这些Scala语句用于Scala代码的条件执行,而不是生成Chisel(多路复用器)硬件。当我们编写电路生成器时,这些Scala条件在Chisel中有它们的用途,电路生成器接受参数以有条件地生成不同的硬件实例。

Decoder 解码器

解码器将n位的二进制数转换成m位信号,其中 m < = 2 n m <= 2^n m<=2n。图5.2显示了一个2位到4位的解码器。我们可以用真值表来描述解码器的功能,如表5.1所示。

Chisel switch语句将逻辑描述为真值表。要使用switch语句,我们需要包含chisel.util包。
在这里插入图片描述
在这里插入图片描述

import chisel3.util._

下面的代码使用Chisel的switch语句来描述解码器:

result := 0.U

switch(sel){
	is (0.U) { result := 1.U }
	is (1.U) { result := 2.U }
	is (2.U) { result := 4.U }
	is (3.U) { result := 8.U }
	}

上述switch语句列出了sel信号的所有可能值,并将解码值分配给结果信号。请注意,即使我们枚举了所有可能的输入值,Chisel仍然需要我们分配一个默认值,就像我们为result分配初始值0一样。此分配永远不会处于活动状态,因此会被后端工具优化掉。它旨在避免组合电路(在Chisel a Wire中)的不完整分配情况,这将导致硬件描述语言(如VHDL和Verilog)中的意外锁存。Chisel不允许不完整的分配。

在前面的例子中,我们使用了无符号整数作为信号。也许编码电路的更清晰的表示使用二进制表示法:

switch (sel) {
	is ("b00".U) {result := "b0001".U}
	is ("b01".U) {result := "b0010".U}
	is ("b10".U) {result := "b0100".U}
	is ("b11".U) {result := "b1000".U}
	}

表给出了解码器函数的非常可读的表示,但也有点冗长。在检查表时,我们看到一个规则的结构:将1左移由SEL表示的数字。因此,我们可以用Chisel移位操作<<来表示解码器。

result := 1.U << sel

解码器用作多路复用器的构建块,其输出用作多路复用器数据输入的AND门的使能。然而,在Chisel中,我们不需要构建多路复用器,因为Mux在核心库中可用。解码器也可用于微处理器的地址总线的一些位的地址解码。输出用作存储器和连接到微处理器的不同IO设备的选择信号。

Encode译码器

编码器将独热编码输入信号转换成二进制编码输出信号。编码器进行解码器的逆操作。

图5.3显示了2位二进制输出编码器的4位one-hot输入,表5.2显示了encode函数的真值表。然而,编码器仅在输入信号被独热编码时按预期工作。对于所有其他输入值,输出未定义。由于我们不能描述一个具有未定义输出的函数,我们使用一个默认赋值来捕获所有未定义的输入模式。
在这里插入图片描述
在这里插入图片描述
下面的Chisel代码分配一个默认值0,然后使用switch语句作为法定的的输入值。

b := "b00".U

switch(a){
	is ("b0001.U") { b:= "b00".U}
	is ("b0010.U") { b:= "b01".U}
	is ("b0100.U") { b:= "b10".U}
	is ("b1000.U") { b:= "b11".U}
	}

对于解码器,我们找到了一个优雅的单行语句来表达逻辑。这也使得能够描述宽解码器。然而,我们不知道编码器的这种表达式。

为了表达更大规模的编码器,我们需要编写一个(简单的)硬件生成器。因此,我们需要引入Scala循环结构。下面两行Scala代码表示一个循环,从0到9计数。

//Loops i from 0 to 9
for (i <- until 10){
	//use i to index into a Wire or Vec
	}

为了表达更大的编码器,我们需要编写一个(简单的)硬件生成器。因此,我们需要引入Scala循环结构。下面两行Scala代码表示一个循环,从0到9计数。

对于编码器生成器,我们将使用Vec,其中每个元素表示编码器表的一列。下面的代码显示了一个16位编码器,其中输出为4位:

val v = Wire(Vec(16,UInt(4.W)))
v(0) := 0.U
for (i <- 1 until 16){
	v(i) := Mux(hotIn(i), i.U, 0.U) | v(i-1)
	}
val encOut = v(15)

编码器的输入为hotIn,输出为encOut。Vec元素0是默认情况(0),并且还表示在hotIn中设置最低有效位(LSB)时的输出值。

Vec元件1至15连接到多路复用器。如果位置i处的位在hotIn中被设置,则多路复用器输出是索引,否则它是0。为了我们的编码器的正确行为,我们假设输入信号是独热编码的。最后,我们需要合并所有向量元素以获得单个输出。由于当输入中的对应位为0时向量元素为0,因此我们可以简单地用OR函数联合收割所有元素。在循环中,我们将当前元素与(…)之前的一个向量元素进行OR。| v(i-1))。当多个元素与一个函数组合在一起时,我们也将此操作称为reduce。因此,在这里我们执行OR缩减。

仲裁

我们使用仲裁器来仲裁来自多个客户端的请求到单个共享资源。示例将是共享单个串行端口(UART)的若干处理器核。

图5.4显示了一个4位仲裁器的原理图。它由四条请求线(r0-r3)和四条授权线(g0-g3)组成。仲裁器仅赠款单个请求。例如,**请求输入0101将导致授权输出0001。**仲裁器优先考虑较低的输入。因此,我们称之为优先级仲裁器。位数越低,优先级越高。

在这里插入图片描述
为了构建一个公平的仲裁器,我们需要添加状态来记住最后一次仲裁。我们将在后面的章节中介绍一个公正的仲裁者。

小的仲裁器也可以直接用逻辑表来描述。以下代码显示了3位仲裁器的表。

val grant = WireDefault("b0000".U(3.W))
switch (request){
	is ("b000".U) { grant := "b000".U}
	is ("b000".U) { grant := "b001".U}
	...
	...
	is ("b111".U) { grant := "b001".U}
	}

然而,对于更大的仲裁电路,我们将使用我们新学到的for循环作为生成器循环的技巧。下面的代码显示了用于n个请求和仲裁的参数化仲裁器。这里我们再次使用布尔向量。这里我们再次使用布尔向量:

val grant = VecInit.fill(n)(false.B)
val notGranted = VecInit.fill(n)(false.B)

grant(0) :=request(0)
notGranted(0) := !grant(0)
for (i <- until n){
	grant(i) := request(i) && notGranted(i-1)
	notGranted(i) := !grant(i) && notGranted(i-1)
	}

上面显示的代码是初始仲裁器版本的循环版本。它为n个请求生成仲裁电路。与手动版本(展开循环)的微小区别在于,我们还为最后一个请求(n 1)生成了一个notGranted连接。该线不被使用,合成工具将优化它。

优先级编码器

在我们最初的编码器设计中,我们必须假设输入是独热(one - hot)编码的,这意味着最大一位允许为1。设置了多个位的输入是非法的,并导致未定义的行为。

我们可以通过组合仲裁电路来解决这个问题,该仲裁电路只选择最高优先级的位集。当我们将仲裁器的输出馈送到编码器时,我们创建了一个优先级编码器。图5.6显示了原理图。
在这里插入图片描述

比较器

在这里插入图片描述
作为组合构建模块章节的最后一个电路,我们将介绍比较器。图5.7显示了比较器的原理图。它有两个多位输入,并比较这两个值。它有两个输出:equ(a=b);gt(a>b)。下面的代码片段显示了比较器。正如您所看到的,这只是两行Chisel代码。因此,比较函数通常只是直接在其他组件中使用,而不是包装到模块中。

val equ = a === b
val gt = a > b
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值