吃透Chisel语言.05.Chisel基础(二)——组合电路与运算符

Chisel基础(二)——组合电路与运算符

组合逻辑电路从数学的角度来讲,就是用布尔代数的操作符来描述的数字逻辑电路,也就是一系列布尔代数运算符的组合。Chisel中,这些布尔代数的操作符跟C、Java、Scala以及其他编程语言中定义的是类似的,比如,&按位与操作符,|按位或操作符。这一部分就详细介绍Chisel中基本的位运算符、算术运算符、逻辑运算符、比较运算符等,以及Chisel中的一个高阶组合电路运算符——多路选择器。

一个简单组合逻辑电路的例子

下面这一行代码,定义了一个组合电路,它用一个与门连接信号ab,然后把这个与门的输出和信号c用或门连接在一起:

val logic = (a & b) | c

这个表达式对应的电路示意图如下:

吃透Chisel语言.05.Chisel基础(二)——组合电路与运算符_第1张图片

可以看到基础语法是很简单的,需要注意的是,这个电路中与门或门的输入信号可以是单个比特,也可以是比特向量。

Chisel中的位运算符

下面的例子分别演示了四种基本的位运算,用的是Scala中的标准运算符,他们的操作数可以是UIntSIntBool

val and = a & b		// 按位与
val or = a | b		// 按位或
val xor = a ^ b		// 按位异或
val not = ~a		// 按位取反

这几种基本的位运算都很基础,还有两个移位操作,他们的操作数可以是UIntSInt

val shiftleft = a << b
val shiftright = a >> b

需要注意的是,对于SInt类型的操作数,右移或会进行符号拓展,即算术右移。

总结Chisel中的位运算符如下:

操作符 描述 数据类型
& 按位与 UIntSIntBool
` ` 按位或
^ 按位异或 UIntSIntBool
~ 按位取反 UIntSIntBool
<< 左移 UIntSInt
>> 对于UInt是逻辑右移,对于SInt是算术右移 UIntSInt

Chisel中的算术运算符

下面是Chisel中使用Scala标准运算符进行的算术运算,他们的操作数可以是UIntSInt

val add = a + b		// 加法
val sub = a - b		// 减法
val neg = -a		// 取相反数
val mul = a * b		// 乘法
val div = a / b		// 除法
val mod = a % b		// 取余

需要注意这里的位宽推断:

  1. 对于加减法,结果宽度为操作数中最宽的那个宽度;
  2. 对于乘法,结果宽度为操作数的宽度之和;
  3. 对于除法和取余,结果宽度通常为被除数的宽度;

另外,对于加法和减法,还可以指定是否进行位宽拓展保留进位,在+-后加上%就是不进行位宽拓展,加上&就是不保留进位,默认是不进行位宽拓展。

总结Chisel中的算术运算符如下:

操作符 描述 数据类型
++% 加(不保留进位) UIntSInt
+& 加(保留进位) UIntSInt
--% 减(不保留进位) UIntSInt
-& 减(保留进位) UIntSInt
* UIntSInt
/ UIntSInt
% 取余 UIntSInt

Chisel中的逻辑运算符

逻辑运算符是针对Bool类型的值进行运算的,有逻辑与逻辑或逻辑非这三种,和Scala以及其他编程语言是类似的:

操作符 描述 数据类型
&& 逻辑与 Bool
` `
! 逻辑非 Bool

Chisel中的比较运算符

对于小于、小于等于、大于和大于等于,Chisel和Scala是一致的,但在等于和不等于上表示不一样。比较运算符的操作数为UIntSInt,总结如下:

操作符 描述 数据类型
> 大于 UIntSInt,返回Bool
>= 大于等于 UIntSInt,返回Bool
< 小于 UIntSInt,返回Bool
<= 小于等于 UIntSInt,返回Bool
=== 等于 UIntSInt,返回Bool
=/= 不等于 UIntSInt,返回Bool

虽然这里的====/=看起来很奇怪,但千万不能弄错,设计者表示这么做是为了让Scala中原有的==!=仍然可用。

Chisel中的规约运算符

这是Chisel中比较好用的运算符,操作数为SIntUInt,对操作数的每一位进行规约运算,返回值为Bool类型,三个规约运算符如下:

操作符 描述 数据类型
.andR 与规约 UIntSInt,返回Bool
.orR 或规约 UIntSInt,返回Bool
.xorR 异或规约 UIntSInt,返回Bool

用法如下:

val allSet = x.andR		// 与规约
val anySet = x.orR		// 或规约
val parity = x.xorR		// 异或规约

Chisel中的位字段操作符

我们前面提到UIntSInt都是位向量,因此应该有一些对向量的位字段进行操作的操作符,上一部分的规约操作符就属于这类。Chisel中还有其他的位字段操作符:

比如从位向量中提取单个比特,操作符为(n),表示提取第n位,最低有效位LSB索引为0:

val xLSB = x(0)		// 提取x的最低位

也可以提取一个位段,操作符为(end, start),表示提取第start位到第end位之间的字段,这个startend是包括在内的,返回值是个UInt

val xTopNibble = x(15, 12)	// 假设x是16位的,提取x的高4位

还可以把一个位向量复制多次,操作符为Fill(n, x)n为复制次数,x为被复制的位向量,只可以是或UInt,返回值也是UInt

val usDebt = Fill(3, "hA".U)	// "hAAA".U

最后是可以拼接多个位向量的操作,操作符为##Cat,和Verilog中的{}类似,示例如下:

val float = Cat(sign, exponent, mantissa)		// 拼接三个向量,或者
val float = sign ## exponent ## mantissa

不过需要注意的是,拼接操作的操作数两边类型必须一样,而返回值为UInt,因此,如果在多个操作数上用##进行拼接的时候需要注意,比如对三个SInt进行拼接就会报错,而对两个SInt和一个UInt进行拼接就不会报错,而使用Cat就不会有这个问题。

另外还有个需要注意的是,虽然##Cat功能是类似的,但是生成的Verilog会有所不同,比如:

a := -1.S ## -2.S

会生成:

assign a = {1'sh1,2'sh2};

而:

a := Cat(-1.S, -2.S)

会生成:

assign a = 3'h6;

一般来说使用Cat进行拼接是更好的。

总结如下:

操作符 描述 数据类型
x(n) 提取第n UIntSInt,返回Bool
x(end, start) 提取第start到第end UIntSInt,返回UInt
Fill(n, x) 位向量x复制n UInt,返回UInt
a ## b 位向量拼接 UIntSInt,返回UInt
Cat(a, b, ...) 位向量拼接 UIntSInt,返回UInt

关于Chisel操作符的优先级

Chisel操作符的优先级并没有作为Chisel语言的一部分直接定义出来,而是取决于电路的赋值顺序,自然地遵循Scala的运算符优先级。如果实在拿不准的话,那就使用括号来表达运算优先级。

题外话,Chisel和Scala的运算符优先级和Java/C相似但不同,而Verilog和C是一样的,但是VHDL直接就是没有这个特性的。在VHDL里面,所有的运算符优先级相同,按照从左到右的顺序进行计算。

Chisel中的2-1多路选择器

多路选择器(multiplexer)是在多个输入中选择一个作为输出的组合电路,其最基本的形式就是2-1多路选择器,即二选一。下图就是一个2-1多路选择器,或者简称为Mux:

吃透Chisel语言.05.Chisel基础(二)——组合电路与运算符_第2张图片

根据选择信号sel的值,输出y会表示输入信号ab。当然,我们用逻辑门也是可以实现这个Mux的,但是Chisel标准库里面就提供了Mux作为标准的操作符,示例如下:

val y = Mux(sel, a, b)

sel是个Chisel中的Bool类型值,为true的时候选择输出a,否则选择输出b。这里ab可以是任意的Chisel基本类型或聚合类(比如bundle或vector,后面会详细讲),只要它俩的类型是一样的就行。

结语

有了上面的基本算术、逻辑操作和这里的多路选择器,那就可以描述所有的组合电路了。但是,硬用这些来描述显然不够优雅,比如我要实现一个3-8译码器,我总不能使用8个Mux吧?那代码可读性也太差了!而Chisel里面还提供了更多的组件和控制抽象,能让我们在描述一个组合电路的时候更优雅,相关内容后面详细说!

你可能感兴趣的:(吃透Chisel语言!!!,risc-v,fpga开发,Chisel,计算机体系结构,CPU设计实现)