目录
一、简单例子引入
1.1 端口声明
1.1.2 Verilog实现
在Verilog中,一个2-1选择器的模块定义如下:
module mux(
input S,
input D0,
input D1,
output Y
);
// 内部逻辑实现(这里未展示)
endmodule
这个模块名为mux
,它有四个端口:选择信号S
和两个数据输入D0
、D1
,以及一个数据输出Y
。每个端口的方向通过input
和output
关键字指定。
1.1.3 Chisel实现
在Chisel中,实现相同的2-1选择器模块如下:
import chisel3._
class mux extends Module {
val io = IO(new Bundle {
val S = Input(Bool())
val D0 = Input(Bool())
val D1 = Input(Bool())
val Y = Output(Bool())
})
// 内部模块连线(这里未展示具体的逻辑实现)
}
逐行解释
-
定义模块:
class mux extends Module { ... }
定义了一个名为mux
的类,该类继承自Module
。在Chisel中,所有硬件模块都是通过继承Module
类来定义的。 -
声明端口:
val io = IO(new Bundle { ... })
定义了模块的输入输出端口。IO
是Chisel中用于定义模块接口的方法,它接受一个Bundle
对象作为参数。Bundle
可以看作是Chisel中的结构体,用于组合多个信号。 -
端口方向和数据类型:在
Bundle
内部,使用Input
和Output
方法指定端口的方向,并通过传递数据类型(如Bool()
)来指定端口的数据类型。这里S
、D0
、D1
为输入端口,Y
为输出端口,它们的数据类型都是Bool
,表示1位的布尔信号。 -
数据类型:Chisel支持多种数据类型,如
Bool
和UInt
。Bool
表示1位的二进制信号,而UInt(x.W)
表示宽度为x
的无符号整数,其中W
是宽度的缩写。在本例中,我们只使用了Bool
类型,但Chisel也支持更复杂的数据类型,以满足不同的设计需求。 -
内部逻辑实现:虽然在本例中未展示具体的内部逻辑实现,但通常会在
class
定义的最后部分添加逻辑,以根据输入信号S
、D0
、D1
计算输出信号Y
的值。
这样,我们就完成了在Verilog和Chisel中定义一个2-1选择器模块的基本步骤。注意,在Chisel中,由于它基于Scala语言,所以语法和Verilog有所不同,但基本的设计思想是一致的。
1.2 内部逻辑实现
1.2.1 Verilog实现
在Verilog中,2选1选择器的实现可以直接根据逻辑表达式编写:
module mux(
input S,
input D0,
input D1,
output Y
);
wire X = ~S;
wire Y1 = D0 & X;
wire Y2 = D1 & ~X; // 注意这里应该是 ~X 而不是 X,因为我们需要选择 D0 或 D1
assign Y = Y1 | Y2; // 这里实际上应该使用 Y2 = D1 & ~X 来确保逻辑正确
// 或者更直接地,使用 Y = (S ? D1 : D0);
endmodule
1.2.2 Chisel实现
在Chisel中,我们可以使用Wire
类型来定义中间信号,并使用Scala的运算符来实现逻辑表达式。以下是完整的Chisel实现:
import chisel3._
class mux extends Module {
val io = IO(new Bundle {
val S = Input(Bool())
val D0 = Input(Bool())
val D1 = Input(Bool())
val Y = Output(Bool())
})
val X = Wire(Bool()) // 定义Wire类型,用于中间信号
X := !io.S // 将io.S的值取反后赋值给X
val Y1 = io.D0 & X // 定义并赋值Y1,使用&运算符进行位与操作
val Y2 = io.D1 & !X // 定义并赋值Y2,注意这里应该是!X
io.Y := Y1 | Y2 // 将Y1和Y2进行位或操作,结果赋值给输出Y
// 或者,更简洁地使用Chisel的Mux方法:
// io.Y := Mux(io.S, io.D1, io.D0)
}
Chisel 关键点解释
- Wire 类型:在Chisel中,
Wire
类型用于表示硬件单元之间的物理连线。Wire的值由其连接的器件输出端连续驱动。 - 类型推断:在Chisel中,如果直接写出运算式(如
io.D0 & X
),Chisel会自动推断结果的类型。但显式声明类型(如val X = Wire(Bool())
)可以提高代码的可读性和健壮性。 - 赋值操作符:在Chisel中,
:=
用于表示电路的连接,而=
则通常用于Scala中的值赋值。但在某些情况下,如直接定义Wire的运算表达式时,可以使用=
,因为Chisel会处理这种情况下的类型推断和连接。 - Mux 方法:Chisel提供了
Mux
方法,这是一个高级抽象,可以直接实现多路选择器功能,无需手动编写所有底层逻辑。
1.3 常用的硬件原语
Chisel的一大优势在于它提供了许多常用的硬件模块库,这些库中的模块可以直接被调用,从而大大简化了硬件设计的复杂度。对于上述实现的2选1多路选择器(mux),Chisel已经定义好了一个名为Mux
的函数,该函数位于chisel3
包中。因此,我们不需要从头开始编写选择器的底层逻辑,而是可以直接使用Mux
函数来实现。
以下是使用Mux
函数实现的2选1多路选择器的代码示例:
import chisel3._
class mux_2 extends Module {
val io = IO(new Bundle {
val S = Input(Bool()) // 选择信号
val D0 = Input(Bool()) // 数据输入0
val D1 = Input(Bool()) // 数据输入1
val Y = Output(Bool()) // 数据输出
})
// 使用Mux函数实现多路选择器
// 当io.S为true(即高电平)时,选择io.D0作为输出;当io.S为false(即低电平)时,选择io.D1作为输出
io.Y := Mux(io.S, io.D0, io.D1)
}
在这段代码中,Mux
函数接收三个参数:第一个参数是选择信号io.S
,它是一个布尔类型的输入;第二个和第三个参数分别是两个数据输入io.D0
和io.D1
,它们也都是布尔类型的输入。Mux
函数根据选择信号的值来决定输出io.Y
是接收io.D0
的值还是io.D1
的值。
Mux
函数是硬件描述中非常常用的一个组件,它简化了多路选择逻辑的实现,使得硬件设计更加简洁和高效。在Chisel中,通过使用这些封装好的模块,开发者可以更加专注于系统级的设计,而不是陷入底层硬件逻辑的细枝末节中。
二、Chisel主要数据类型介绍
2.1 数据类型
在Chisel中,常用的数据类型包括UInt
、SInt
和Bool
。这些数据类型用于表示电路中的信号,其中UInt
用于无符号整数,SInt
用于有符号整数(按补码解读),而Bool
则用于表示单个位的布尔值。
-
UInt:可以表示电路中任意位宽的线网或寄存器。在Chisel中,
UInt
字面量可以通过在整数值后添加.U
后缀来构造,例如1.U
表示字面值为1的UInt
对象。此外,还可以使用Scala的BigInt
、Int
、Long
类型的字面量,并通过隐式转换来构造UInt
对象,这些字面量默认是十进制的,但可以通过前缀0x
或0X
指定为十六进制。 -
SInt:在Chisel中按补码解读,转换成Verilog后使用系统函数
$signed
。SInt
字面量可以通过在整数值(包括负数)后添加.S
后缀来构造,例如-8.S
表示字面值为-8的SInt
对象。 -
Bool:用于表示1位宽的布尔信号。在Chisel中,
Bool
字面量可以通过Scala的Boolean
类型字面量(true
或false
)来构造,然后调用.B
方法进一步得到Bool
类型的对象,例如true.B
。
Chisel定义了一系列隐式类来支持从Scala基本类型到Chisel数据类型的隐式转换,如fromBigIntToLiteral
、fromIntToLiteral
等。这些隐式类提供了U
和S
方法,分别用于构造等值的UInt
和SInt
对象。此外,Chisel还支持从字符串字面量构造UInt
对象,字符串可以包含前缀h
、o
、b
来分别表示十六进制、八进制和二进制,且可以使用下划线进行分隔以提高可读性。
示例:
1.U
// 字面值为1的UInt
对象0xd.U
// 字面值为13(十六进制)的UInt
对象-8.S
// 字面值为-8的SInt
对象"b01_01".U
// 字面值为5(二进制)的UInt
对象true.B
// 字面值为true
的Bool
对象
这些字面量表示方法使得在Chisel中定义电路信号时更加直观和方便。
2.2 数据宽度
在Chisel中,数据宽度的处理非常灵活。默认情况下,字面量构造的UInt
和SInt
对象的宽度会根据字面值自动选择最小可能的位宽,例如字面值为8
的UInt
对象默认为4位宽(因为2^3 = 8
,但考虑到符号位,实际上不会这样,这里可能是个误导,通常直接表示为4位的二进制表示,即00001000
),而SInt
对象由于需要表示负数,位宽会相应增加。然而,也可以显式指定数据的宽度。
Chisel通过Width
类以及隐式类fromIntToWidth
来支持宽度的显式指定。fromIntToWidth
允许将Int
对象隐式转换为fromIntToWidth
类型的对象,进而通过W
方法返回一个Width
对象。UInt
和SInt
的构造方法(如U
、asUInt
、S
、asSInt
)都有重载版本,这些版本接收一个Width
类型的参数来构造指定宽度的对象。
需要注意的是,Bool
类型固定为1位宽,因此不能显式指定其宽度。
以下是一些示例:
1.U
// 字面值为1,宽度为1bit的UInt
对象1.U(32.W)
// 字面值为1,但显式指定宽度为32bit的UInt
对象1.U(32)
// 这里有一个错误,因为.U
方法不接受单个整数作为宽度参数,正确的方式是使用32.W
1.U(0)
// 这同样是不正确的,因为.U
后面不能直接跟整数0作为宽度,应使用0.W
(但通常不会这样做,因为0宽没有意义)
关于无字面量的对象构造,UInt
和SInt
的apply
工厂方法允许通过指定宽度来构造对象,这对于声明端口、构造向量等场景非常有用。同时,也存在无参版本的apply
方法,它们会尝试自动推断宽度(但通常用于字面量构造)。
结合数据类型和赋值方法,我们可以在模块内部进行如下声明和赋值:
val v0 = Wire(UInt(2.W)) // 声明一个2位宽的Wire,类型为UInt
val v1 = Wire(UInt(4.W)) // 声明一个4位宽的Wire,类型为UInt
val b0 = Wire(Bool()) // 声明一个Bool类型的Wire,固定为1位宽
val b1 = Wire(Bool()) // 另一个Bool类型的Wire
v0 := 2.U // 将v0赋值为字面值2的UInt对象,自动推断为最小宽度,但这里明确是2位宽
v1 := "b01_01".U // 将v1赋值为二进制字面值"01_01"的UInt对象,这里显式指定了v1的位宽为4位
b0 := 1.U // 将b0赋值为字面值1的UInt对象,由于Bool是1位宽,这里实际上是将UInt值截断到最低位
b1 := true.B // 将b1赋值为Boolean字面量true的Bool对象,与1.U等价
请注意,尽管Bool
可以被视为1位宽的UInt
,但在实际使用中应保持类型的明确性,特别是在涉及到位宽敏感的场合。
2.3 数据转换
在Chisel中,UInt
、SInt
和Bool
类型提供了asUInt
、asSInt
、asBool
和asBools
方法用于类型转换。这些方法允许在不同数据类型之间进行转换,但需要注意转换过程中可能发生的符号位和数值变化。
asUInt
:将对象转换为无符号整数(UInt
)。如果原始对象是SInt
,则转换时保留其二进制表示不变,但解释方式变为无符号,可能会导致数值上的变化(如正负数表示)。asSInt
:将对象转换为有符号整数(SInt
)。如果原始对象是UInt
,则直接按照其二进制表示解释为有符号数,可能需要考虑扩展符号位。asBool
:仅当对象为1位宽时有效,将UInt
或SInt
的1或0值转换为Bool
类型的true
或false
。asBools
:将多位宽的UInt
或SInt
对象的每一位转换成一个Bool
序列(Seq[Bool]
),用于逐位操作。
示例中的类型转换如下:
-
"ha".asUInt(8.W)
// 这里有一个错误,因为"ha"
不是Chisel直接支持的字符串字面量格式。正确的十六进制表示应该使用前缀0x
,如0xha
(但注意ha
不是有效的十六进制数,这里假设是0xa
的误写)。正确的代码是:"0xa".U(8.W)
,这将构造一个字面值为0xa
(即十进制中的10),宽度为8bit的UInt
对象。 -
1.S(3.W).asUInt
// 正确。这表示构造一个字面值为1,宽度为3bit的有符号整数(SInt
),然后将其转换为无符号整数(UInt
)。由于原始值是正数且位宽未超过表示范围,转换后的值仍然是1,但类型为UInt
。
注意:在Chisel中,字符串到UInt
或SInt
的转换通常通过前缀(如0x
表示十六进制,0b
或b
表示二进制,0o
或o
表示八进制,但八进制前缀在Chisel中可能不完全支持,具体取决于版本和上下文)和.U
或.S
后缀来实现,而不是直接调用asUInt
或asSInt
方法(这些方法用于类型之间的转换,而不是从字符串构造对象)。字符串"ha"
在Chisel中不能直接转换为数字,因为它不是一个有效的数字表示。如果意图是表示十六进制数a
(即十进制中的10),则应该使用"0xa"
或0xa.U
(指定宽度时加上.W
后缀)。
2.4 运算类型
在Chisel中,对硬件描述语言(HDL)中的常见运算进行了抽象,以支持更高级别的硬件设计。以下是您提供的运算类型及其在Chisel中的解释,包括它们如何影响信号或值的位宽,以及(如果适用)这些运算在Verilog中的等价表示。
逻辑运算
!x
:逻辑非,结果为1位宽。在Chisel中,直接对信号使用!
运算符。x && y
:逻辑与,但在Chisel中通常使用x & y
进行位与操作,逻辑与在布尔上下文中使用。结果位宽为1,但在位运算中,结果位宽取决于操作数的最大位宽。x || y
:逻辑或,同样,在Chisel中布尔上下文中使用|
进行位或操作。结果位宽为1。
位操作运算符
~x
:位反,结果位宽与x
相同。在Verilog中为~signal_x
。x & y
:位与,结果位宽为max(w(x), w(y))
。在Verilog中为signal_x & signal_y
。x | y
:位或,结果位宽同样为max(w(x), w(y))
。在Verilog中为signal_x | signal_y
。x ^ y
:按位异或,结果位宽也为max(w(x), w(y))
。在Verilog中为signal_x ^ signal_y
。x(n)
:位索引,获取x
的第n
位(从0开始计数,0是最低位)。在Chisel中直接使用索引语法。x(n, m)
:字段提取,提取x
中从n
到m
的位(包含n
和m
),结果位宽为n - m + 1
。在Verilog中为signal_x[n:m]
。x << n
:左移,结果位宽为w(x) + n
。在Verilog中为signal_x << n
。x >> n
:右移(补零),结果位宽为w(x) - n
(但实际操作中可能保持原位宽,并丢弃高位)。在Verilog中为signal_x >> n
,但注意Verilog中的右移可能依赖于信号的符号。Fill(n, x)
:重复拼接x
,结果位宽为n * w(x)
。在Verilog中没有直接等价,但可以用重复拼接的语法{n{signal_x}}
表示。Cat(x, y)
:拼接x
和y
,结果位宽为w(x) + w(y)
。在Verilog中为{signal_x, signal_y}
。Mux(c, x, y)
:三元操作(多路选择器),结果位宽为max(w(x), w(y))
。在Verilog中为signal_c ? signal_x : signal_y
。
关系运算符
这些运算符通常用于布尔表达式,结果位宽为1。
x === y
:相等(triple equals,在Chisel中可能用于精确匹配,包括位宽和值)。在Verilog中为signal_x == signal_y
,但注意===
在Verilog中有不同的含义(通常用于比较四个状态的值)。x != y
、x =/= y
:不等,结果相同。在Verilog中为signal_x != signal_y
。x > y
、x >= y
、x < y
、x <= y
:大于、大于等于、小于、小于等于。
算数运算符
x + y
:加,结果位宽为max(w(x), w(y))
。在Verilog中为signal_x + signal_y
。x +& y
、x -& y
:位扩加和位扩减,这些在Chisel中可能不直接对应,但在某些上下文(如固定点运算)中可能使用。Verilog中没有直接等价。x - y
:减,结果位宽同样为max(w(x), w(y))
。在Verilog中为signal_x - signal_y
。x * y
:乘,结果位宽为w(x) + w(y)
(但可能需要额外的位宽以避免溢出)。在Verilog中为signal_x * signal_y
。x / y
:除,结果位宽通常与x
相同(但可能依赖于实现和精度要求)。在Verilog中为signal_x / signal_y
,但注意Verilog的除法可能与硬件实现有关。x % y
:取余,结果位宽取决于y
的最大可能值减1的位数。在Verilog中为signal_x % signal_y
。
2.5 向量(Vec)和包裹(Bundle)类型
2.5.1 Vec 类型
Vec[T]
是一个固定大小的向量(或数组),其中 T
是向量的元素类型,如 UInt
、SInt
或 Bool
等。所有元素的类型和位宽必须相同。Vec
提供了对元素的索引访问,使得在硬件设计中处理多个相同类型的信号变得非常方便。
使用场景:
- 当你需要处理多个相同类型和位宽的信号时(如多个数据通道、寄存器组等)。
- 在实现多路选择器(MUX)、寄存器文件或其他需要选择多个输入之一的逻辑时。
val myVec = Wire(Vec(3, UInt(2.W)))
myVec(0) := 0.U
myVec(1) := 1.U
myVec(2) := 2.U
在这个例子中,myVec
是一个包含3个2位宽无符号整数的向量。通过索引可以直接访问和修改这些元素。
2.5.2 Bundle 类型
Bundle
类似于C语言中的结构体,它允许你将多个不同类型的字段组合成一个单一的复合类型。Bundle
非常适合用于定义模块的端口列表或模块内部需要一起管理的信号集合。
使用场景:
- 定义模块的IO端口。
- 在模块内部组织相关信号,以提高代码的可读性和可维护性。
示例:
class MyModule extends Module {
val io = IO(new Bundle {
val in = Input(UInt(32.W))
val out = Output(UInt(32.W))
val enable = Input(Bool())
})
// 模块逻辑...
}
在这个例子中,MyModule
的端口列表被定义为一个 Bundle
,包含了输入信号 in
和 enable
,以及输出信号 out
。这种方式使得端口的组织更加清晰,同时也便于在模块内部引用这些端口。
2.5.3 Vec 和 Bundle 的结合使用
在更复杂的设计中,Vec
和 Bundle
可以结合使用来构建更加复杂的数据结构和模块接口。例如,你可以定义一个 Vec
,其元素类型为自定义的 Bundle
,从而在一个向量中管理多个具有复杂结构的元素。
示例:
class ComplexSignal extends Bundle {
val data = UInt(32.W)
val valid = Bool()
}
val signals = Vec(3, new ComplexSignal())
signals(0).data := 0.U
signals(0).valid := true.B
// ...
在这个例子中,signals
是一个包含3个 ComplexSignal
元素的向量。每个 ComplexSignal
都包含了一个32位宽的数据字段和一个有效标志位。这种方式允许你在单个向量中管理多个复杂信号,从而提高了设计的模块化和可重用性。
三、主要硬件类型和连线
3.1 模块(Module)定义
// 定义一个名为 AndModule 的类,它继承自 Chisel 的 Module 类
class AndModule extends Module {
// 实现 Module 类的抽象成员 io,它是一个 Bundle,用于定义模块的接口
val io = IO(new Bundle {
// 在 Bundle 内部声明输入端口 a 和 b,它们都是 Bool 类型
val a = Input(Bool())
val b = Input(Bool())
// 声明输出端口 c,它也是 Bool 类型
val c = Output(Bool())
})
// 在模块的体内部,使用 Chisel 的硬件赋值语句将输出端口 c 赋值为输入端口 a 和 b 的逻辑与
io.c := io.a & io.b
}
关键点说明:
-
继承 Module 类:
AndModule
类通过继承Module
类成为了一个 Chisel 模块。 -
隐式时钟和复位:虽然在这个简单的组合逻辑示例中没有直接使用到时钟(
clock
)和复位(reset
)信号,但它们是Module
类的一部分,可以在需要时通过构造函数参数或其他方式引入。 -
实现抽象成员 io:每个
Module
都必须实现一个名为io
的抽象成员,它是一个Bundle
,用于定义模块的接口。在这个Bundle
中,你可以声明输入(Input
)和输出(Output
)端口,以及其他任何你需要的信号。 -
输入和输出端口:在
Bundle
内部,你声明了两个输入端口a
和b
,以及一个输出端口c
。输入端口用于接收外部信号,而输出端口用于向外部发送信号。虽然在这个例子中你“使用”了输入端口(通过将它们与输出端口进行逻辑与操作),但理论上即使你声明了输入端口而不直接使用它们,Chisel 也不会报错。然而,对于输出端口,你必须为它们赋值,因为它们是模块向外部世界提供信息的唯一途径。 -
硬件赋值:在模块的体内部,你使用
:=
操作符将io.c
赋值为io.a & io.b
。这是 Chisel 中的硬件赋值语句,它表示在硬件层面上,输出c
的值将始终等于输入a
和b
的逻辑与结果。
3.2 端口
在Chisel中,定义一个模块前首先需要定义好端口。整个端口列表是通过调用IO
法来定义的,其中参数通常是一个Bundle
类型的对象。对于继承自Module
的模块,这个端口列表被引用为io
。由于端口具有方向性(输入或输出),因此需要Input
和Output
方法来为每个端口指定具体的方向。注意,这里的source
参数是数据类型,而不是已经实例化的硬件对象。
一旦端口列表定义完成,就可以通过io.xxx
来访问和使用这些端口。输入端口可以被用来驱动模块内部的信号,而输出端口则可以被其他模块或信号驱动。
例如,定义一个包含向量输入和单个输出的Bundle
:
class MyIO extends Bundle {
val in = Input(Vec(5, UInt(32.W))) // 5个32位无符号整数组成的向量输入
val out = Output(UInt(32.W)) // 32位无符号整数的输出
}
然后,使用这个Bundle
来定义模块的端口列表:
class MyModule extends Module {
val io = IO(new MyIO) // 使用MyIO作为模块的端口列表
// 在这里可以添加模块的逻辑
}
对于两个相连的模块,如果它们的端口名称相同但方向相反,为了避免重复定义端口,Chisel提供了Flipped
方法。这个方法可以翻转Bundle
中所有端口的方向,即将输入端口转换为输出端口,将输出端口转换为输入端口。
例如,使用前面定义的MyIO
,但翻转其端口方向:
class MyModule_1 extends Module {
val io = IO(new MyIO) // in 是输入,out 是输出
// 模块逻辑...
}
class MyModule_2 extends Module {
val io = IO(Flipped(new MyIO)) // 此时,原MyIO中的out变为输入,in变为输出
// 模块逻辑,可能需要适应翻转后的端口方向
}
3.3 硬件类型
模块内部主要有 Wire(线网)和 Reg(寄存器)两种数据类型,他们分别用于描述数字电路里的组合逻辑和时序逻辑。Wire 之间的连线是不具有周期延迟,而 Reg 的输出和输出之间有一周期的延迟时间。
3.4 赋值方法
在Chisel中,由于硬件电路的本质是不可变的,因此所有的硬件对象都是通过val
类型的变量来引用的。这意味着一旦一个变量在初始化时绑定了一个硬件对象,它的引用就不能改变指向另一个对象。然而,这并不意味着该硬件对象本身不能被更新或修改。为了支持这种更新机制,Chisel引入了特殊的赋值方法:=
,它不同于Scala中的=
赋值运算符。
例如,在定义了两个Wire
类型的变量x
和y
之后,我们可以使用:=
操作符来更新它们的值。这里,:=
是Chisel中定义的特殊赋值方法,用于更新硬件对象的状态,而不是改变变量的引用。
// 定义两个4位宽的Wire类型变量
val x = Wire(UInt(4.W))
val y = Wire(UInt(4.W))
// 使用":="操作符向x赋予一个4位的无符号数10(二进制表示为"b1010")
x := "b1010".U
// 将x的值按位取反,并将结果赋给y
y := ~x
需要注意的是,虽然":="
操作符在语法上与=
类似,但它们在语义上有着根本的区别。在Chisel中,":="
用于硬件赋值,而=
则用于变量初始化或数据赋值(这些赋值不会被编译成电路逻辑)。
3.5 选择器Mux
在Chisel中,多路选择器(MUX)是一个常用的电路模块,用于根据选择信号从多个输入中选择一个输出。Chisel提供了几种不同的多路选择器实现方式,每种都有其特定的用途和语法。以下是整理后的内容,尽量保持原信息的完整性和准确性:
3.5.1. Mux(二输入多路选择器)
Mux
是最基本的多路选择器,位于chisel3
包中。它接受一个布尔类型的选择信号sel
和两个相同类型的数据输入in1
、in2
。当sel
为true.B
时,返回in1
;否则返回in2
。
Mux(sel, in1, in2)
Mux
可以内嵌使用,以构建多输入多路选择器,类似于嵌套的三元操作符。
3.5.2. MuxCase(多路选择器)
MuxCase
是chisel3.util
包中提供的一个多路选择器的简便写法,用于处理多个选择条件。它接受一个默认值和一个条件-值对数组。
import chisel3.util._
MuxCase(default, Array(c1 -> a, c2 -> b, ...))
这里,default
是当所有条件都不满足时返回的默认值,数组中的每个元素都是一个对偶,包含条件和对应的值。
3.5.3. MuxLookup(查找表多路选择器)
MuxLookup
是MuxCase
的一个变体,它将选择条件简化为从0开始的索引值,类似于查找表。它也位于chisel3.util
包中。
MuxLookup(idx, defaultArray(0.U -> a, 1.U -> b, ...))
这相当于使用idx
作为索引,从defaultArray
中查找对应的值。如果idx
不在有效范围内,则返回defaultArray
中的默认值(如果有的话)。
3.5.4. Mux1H(独热码多路选择器)
Mux1H
是chisel3.util
包中的独热码多路选择器,它接受一个独热码作为选择信号,并从提供的值中选择一个输出。如果零个或多个选择信号有效,则行为不确定。
val hotValue = Mux1H(io.selector, Seq(2.U, 4.U, 8.U, 11.U))
// 或者
val hotValue = Mux1H(Seg(io.selector(0), io.selector(1), ...), Seq(2.U, 4.U, ...))
// 或者
val hotValue = Mux1H(Seq(io.selector(0) -> 2.U, io.selector(1) -> 4.U, ...))
在Mux1H
中,io.selector
是一个UInt
类型的数据,其位宽应不小于待选择数据的个数。Mux1H
会从低到高依次将io.selector
的每一位作为选择信号,并与提供的被选择数据一一对应。