【数字集成电路与系统设计】基本的组合逻辑电路

目录

一、简单例子引入

1.1 端口声明

1.1.2 Verilog实现

1.1.3 Chisel实现

逐行解释

1.2 内部逻辑实现

1.2.1 Verilog实现

1.2.2 Chisel实现

Chisel 关键点解释

1.3 常用的硬件原语

二、Chisel主要数据类型介绍

2.1 数据类型

2.2 数据宽度

2.3 数据转换

2.4 运算类型

逻辑运算

位操作运算符

关系运算符

算数运算符

2.5 向量(Vec)和包裹(Bundle)类型

2.5.1 Vec 类型

2.5.2 Bundle 类型

2.5.3 Vec 和 Bundle 的结合使用

三、主要硬件类型和连线

3.1 模块(Module)定义

3.2 端口

3.3 硬件类型

3.4 赋值方法

3.5 选择器Mux

3.5.1. Mux(二输入多路选择器)

3.5.2. MuxCase(多路选择器)

3.5.3. MuxLookup(查找表多路选择器)

3.5.4. Mux1H(独热码多路选择器)


一、简单例子引入

1.1 端口声明

1.1.2 Verilog实现

 在Verilog中,一个2-1选择器的模块定义如下:

module mux(  
    input S,   
    input D0,   
    input D1,   
    output Y  
);  
    // 内部逻辑实现(这里未展示)  
endmodule

这个模块名为mux,它有四个端口:选择信号S和两个数据输入D0D1,以及一个数据输出Y。每个端口的方向通过inputoutput关键字指定。

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())  
  })  
    
  // 内部模块连线(这里未展示具体的逻辑实现)  
}

逐行解释

  1. 定义模块class mux extends Module { ... }定义了一个名为mux的类,该类继承自Module。在Chisel中,所有硬件模块都是通过继承Module类来定义的。

  2. 声明端口val io = IO(new Bundle { ... })定义了模块的输入输出端口。IO是Chisel中用于定义模块接口的方法,它接受一个Bundle对象作为参数。Bundle可以看作是Chisel中的结构体,用于组合多个信号。

  3. 端口方向和数据类型:在Bundle内部,使用InputOutput方法指定端口的方向,并通过传递数据类型(如Bool())来指定端口的数据类型。这里SD0D1为输入端口,Y为输出端口,它们的数据类型都是Bool,表示1位的布尔信号。

  4. 数据类型:Chisel支持多种数据类型,如BoolUIntBool表示1位的二进制信号,而UInt(x.W)表示宽度为x的无符号整数,其中W是宽度的缩写。在本例中,我们只使用了Bool类型,但Chisel也支持更复杂的数据类型,以满足不同的设计需求。

  5. 内部逻辑实现:虽然在本例中未展示具体的内部逻辑实现,但通常会在class定义的最后部分添加逻辑,以根据输入信号SD0D1计算输出信号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.D0io.D1,它们也都是布尔类型的输入。Mux函数根据选择信号的值来决定输出io.Y是接收io.D0的值还是io.D1的值。

Mux函数是硬件描述中非常常用的一个组件,它简化了多路选择逻辑的实现,使得硬件设计更加简洁和高效。在Chisel中,通过使用这些封装好的模块,开发者可以更加专注于系统级的设计,而不是陷入底层硬件逻辑的细枝末节中。

二、Chisel主要数据类型介绍

2.1 数据类型

在Chisel中,常用的数据类型包括UIntSIntBool。这些数据类型用于表示电路中的信号,其中UInt用于无符号整数,SInt用于有符号整数(按补码解读),而Bool则用于表示单个位的布尔值。

  • UInt:可以表示电路中任意位宽的线网或寄存器。在Chisel中,UInt字面量可以通过在整数值后添加.U后缀来构造,例如1.U表示字面值为1的UInt对象。此外,还可以使用Scala的BigIntIntLong类型的字面量,并通过隐式转换来构造UInt对象,这些字面量默认是十进制的,但可以通过前缀0x0X指定为十六进制。

  • SInt:在Chisel中按补码解读,转换成Verilog后使用系统函数$signedSInt字面量可以通过在整数值(包括负数)后添加.S后缀来构造,例如-8.S表示字面值为-8的SInt对象。

  • Bool:用于表示1位宽的布尔信号。在Chisel中,Bool字面量可以通过Scala的Boolean类型字面量(truefalse)来构造,然后调用.B方法进一步得到Bool类型的对象,例如true.B

Chisel定义了一系列隐式类来支持从Scala基本类型到Chisel数据类型的隐式转换,如fromBigIntToLiteralfromIntToLiteral等。这些隐式类提供了US方法,分别用于构造等值的UIntSInt对象。此外,Chisel还支持从字符串字面量构造UInt对象,字符串可以包含前缀hob来分别表示十六进制、八进制和二进制,且可以使用下划线进行分隔以提高可读性。

示例:

  1. 1.U // 字面值为1的UInt对象
  2. 0xd.U // 字面值为13(十六进制)的UInt对象
  3. -8.S // 字面值为-8的SInt对象
  4. "b01_01".U // 字面值为5(二进制)的UInt对象
  5. true.B // 字面值为trueBool对象

这些字面量表示方法使得在Chisel中定义电路信号时更加直观和方便。

2.2 数据宽度

在Chisel中,数据宽度的处理非常灵活。默认情况下,字面量构造的UIntSInt对象的宽度会根据字面值自动选择最小可能的位宽,例如字面值为8UInt对象默认为4位宽(因为2^3 = 8,但考虑到符号位,实际上不会这样,这里可能是个误导,通常直接表示为4位的二进制表示,即00001000),而SInt对象由于需要表示负数,位宽会相应增加。然而,也可以显式指定数据的宽度。

Chisel通过Width类以及隐式类fromIntToWidth来支持宽度的显式指定。fromIntToWidth允许将Int对象隐式转换为fromIntToWidth类型的对象,进而通过W方法返回一个Width对象。UIntSInt的构造方法(如UasUIntSasSInt)都有重载版本,这些版本接收一个Width类型的参数来构造指定宽度的对象。

需要注意的是,Bool类型固定为1位宽,因此不能显式指定其宽度。

以下是一些示例:

  1. 1.U // 字面值为1,宽度为1bit的UInt对象
  2. 1.U(32.W) // 字面值为1,但显式指定宽度为32bit的UInt对象
  3. 1.U(32) // 这里有一个错误,因为.U方法不接受单个整数作为宽度参数,正确的方式是使用32.W
  4. 1.U(0) // 这同样是不正确的,因为.U后面不能直接跟整数0作为宽度,应使用0.W(但通常不会这样做,因为0宽没有意义)

关于无字面量的对象构造,UIntSIntapply工厂方法允许通过指定宽度来构造对象,这对于声明端口、构造向量等场景非常有用。同时,也存在无参版本的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中,UIntSIntBool类型提供了asUIntasSIntasBoolasBools方法用于类型转换。这些方法允许在不同数据类型之间进行转换,但需要注意转换过程中可能发生的符号位和数值变化。

  • asUInt:将对象转换为无符号整数(UInt)。如果原始对象是SInt,则转换时保留其二进制表示不变,但解释方式变为无符号,可能会导致数值上的变化(如正负数表示)。
  • asSInt:将对象转换为有符号整数(SInt)。如果原始对象是UInt,则直接按照其二进制表示解释为有符号数,可能需要考虑扩展符号位。
  • asBool:仅当对象为1位宽时有效,将UIntSInt的1或0值转换为Bool类型的truefalse
  • asBools:将多位宽的UIntSInt对象的每一位转换成一个Bool序列(Seq[Bool]),用于逐位操作。

示例中的类型转换如下:

  1. "ha".asUInt(8.W) // 这里有一个错误,因为"ha"不是Chisel直接支持的字符串字面量格式。正确的十六进制表示应该使用前缀0x,如0xha(但注意ha不是有效的十六进制数,这里假设是0xa的误写)。正确的代码是:"0xa".U(8.W),这将构造一个字面值为0xa(即十进制中的10),宽度为8bit的UInt对象。

  2. 1.S(3.W).asUInt // 正确。这表示构造一个字面值为1,宽度为3bit的有符号整数(SInt),然后将其转换为无符号整数(UInt)。由于原始值是正数且位宽未超过表示范围,转换后的值仍然是1,但类型为UInt

注意:在Chisel中,字符串到UIntSInt的转换通常通过前缀(如0x表示十六进制,0bb表示二进制,0oo表示八进制,但八进制前缀在Chisel中可能不完全支持,具体取决于版本和上下文)和.U.S后缀来实现,而不是直接调用asUIntasSInt方法(这些方法用于类型之间的转换,而不是从字符串构造对象)。字符串"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中从nm的位(包含nm),结果位宽为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):拼接xy,结果位宽为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 != yx =/= y:不等,结果相同。在Verilog中为signal_x != signal_y
  • x > yx >= yx < yx <= y:大于、大于等于、小于、小于等于。

算数运算符

  • x + y:加,结果位宽为max(w(x), w(y))。在Verilog中为signal_x + signal_y
  • x +& yx -& 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 是向量的元素类型,如 UIntSInt 或 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  
}

关键点说明

  1. 继承 Module 类AndModule 类通过继承 Module 类成为了一个 Chisel 模块。

  2. 隐式时钟和复位:虽然在这个简单的组合逻辑示例中没有直接使用到时钟(clock)和复位(reset)信号,但它们是 Module 类的一部分,可以在需要时通过构造函数参数或其他方式引入。

  3. 实现抽象成员 io:每个 Module 都必须实现一个名为 io 的抽象成员,它是一个 Bundle,用于定义模块的接口。在这个 Bundle 中,你可以声明输入(Input)和输出(Output)端口,以及其他任何你需要的信号。

  4. 输入和输出端口:在 Bundle 内部,你声明了两个输入端口 a 和 b,以及一个输出端口 c。输入端口用于接收外部信号,而输出端口用于向外部发送信号。虽然在这个例子中你“使用”了输入端口(通过将它们与输出端口进行逻辑与操作),但理论上即使你声明了输入端口而不直接使用它们,Chisel 也不会报错。然而,对于输出端口,你必须为它们赋值,因为它们是模块向外部世界提供信息的唯一途径。

  5. 硬件赋值:在模块的体内部,你使用 := 操作符将 io.c 赋值为 io.a & io.b。这是 Chisel 中的硬件赋值语句,它表示在硬件层面上,输出 c 的值将始终等于输入 a 和 b 的逻辑与结果。

3.2 端口

在Chisel中,定义一个模块前首先需要定义好端口。整个端口列表是通过调用IO法来定义的,其中参数通常是一个Bundle类型的对象。对于继承自Module的模块,这个端口列表被引用为io。由于端口具有方向性(输入或输出),因此需要InputOutput方法来为每个端口指定具体的方向。注意,这里的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类型的变量xy之后,我们可以使用:=操作符来更新它们的值。这里,:=是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和两个相同类型的数据输入in1in2。当seltrue.B时,返回in1;否则返回in2

Mux(sel, in1, in2)

Mux可以内嵌使用,以构建多输入多路选择器,类似于嵌套的三元操作符。

3.5.2. MuxCase(多路选择器)

MuxCasechisel3.util包中提供的一个多路选择器的简便写法,用于处理多个选择条件。它接受一个默认值和一个条件-值对数组。

import chisel3.util._  
  
MuxCase(default, Array(c1 -> a, c2 -> b, ...))

这里,default是当所有条件都不满足时返回的默认值,数组中的每个元素都是一个对偶,包含条件和对应的值。

3.5.3. MuxLookup(查找表多路选择器)

MuxLookupMuxCase的一个变体,它将选择条件简化为从0开始的索引值,类似于查找表。它也位于chisel3.util包中。

MuxLookup(idx, defaultArray(0.U -> a, 1.U -> b, ...))

这相当于使用idx作为索引,从defaultArray中查找对应的值。如果idx不在有效范围内,则返回defaultArray中的默认值(如果有的话)。

3.5.4. Mux1H(独热码多路选择器)

Mux1Hchisel3.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的每一位作为选择信号,并与提供的被选择数据一一对应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

看未来捏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值