chisel5 顺序构建块

本文介绍了时序电路的基础,特别是同步时序电路,其中寄存器是关键组件。Chisel作为一种硬件描述语言,用于定义寄存器和计数器等时序逻辑。寄存器在时钟上升沿更新输出,而计数器则通过累加或条件递增实现不同类型的计数功能。此外,文章还讨论了移位寄存器和存储器的设计,包括同步存储器的特点和FPGA中的实现。

时序电路是输出取决于输入和先前值的电路。由于我们对同步设计(时钟设计)感兴趣,因此当我们谈论时序电路时,我们指的是同步时序电路。为了构建时序电路,我们需要可以存储状态的元件:所谓的寄存器。

寄存器

构建时序电路的基本元件是寄存器。寄存器是D触发器的集合。D触发器在时钟的上升沿捕获其输入的值,并将其存储在其输出。或者,换句话说:寄存器在时钟的上升沿用输入的值更新其输出。

图6.1显示了寄存器的示意符号。时钟信号和重置信号一般在Chisel不声明。

在Chisel中,具有输入d和输出q的寄存器定义为:

val q = RegNext(d)

寄存器的输入和输出可以是由向量和束的组合构成的任意复杂类型。

也可以通过两个步骤定义和使用寄存器:

val delayReg = Reg(UInt(4.W))
delayReg := delayIn

首先,我们定义寄存器并给予它一个名字。其次,我们将信号delayIn连接到寄存器的输入端。还要注意,寄存器的名称包含字符串Reg。为了容易区分组合电路和时序电路,通常的做法是将标记Reg作为名称的一部分。另外,请注意,Scala中的名称(因此也包括Chisel中的名称)通常使用CamelCase。变量名以小写字母开头,类以大写字母开头。

寄存器可以在复位时初始化。复位信号作为时钟信号隐含在Chisel中。我们将重置值(例如零)作为参数提供给寄存器构造函数RegInit。寄存器的输入与Chisel赋值语句连接。

val valReg = RegInit(0.U(4.W))
valReg := inVal

Chisel中复位的默认实现是同步复位。对于同步复位,D触发器不需要任何更改,只需在输入端添加一个多路复用器,在复位时的初始值和数据值之间进行选择。图6.2所示为具有同步复位的寄存器原理图,其中复位驱动多路复用器。然而,由于经常使用同步复位,所以现代FPGA触发器包含到触发器的同步复位(和设置)输入,以不浪费用于多路复用器的LUT资源。

计数器

在这里插入图片描述
最基本的时序电路之一是计数器。在其最简单的形式中,计数器是寄存器,其中输出连接到加法器,并且加法器的输出连接到寄存器的输入。图6.6显示了这样一个自由运行的计数器。

具有4位寄存器的自由运行计数器从0计数到15,然后再次返回到0。计数器也应重置为已知值。

val cntReg = RegInit(0.U(4.W))
cntReg := cntReg + 1.U

当我们想对事件计数时,我们使用一个条件来递增计数器,如图6.7和下面的代码所示。

val cntEventsReg = RegInit(0.U(4.W))
when(event){
	cntEventsReg := cntEventsReg +1.U
	}

在这里插入图片描述

Counting Up and Down(上下倒数)

为了计数到一个值,然后从0重新开始,我们需要将计数器值与最大常量进行比较,例如,使用when条件语句。

  //- start when_counter
  val cntReg = RegInit(0.U(8.W))

  cntReg := cntReg + 1.U
  when(cntReg === N) {
    cntReg := 0.U
  }

我们也可以使用多路复用器作为计数器:

  //- start mux_counter
  val cntReg = RegInit(0.U(8.W))

  cntReg := Mux(cntReg === N, 0.U, cntReg + 1.U)
  //- end

如果我们处于倒计时的状态,我们首先用最大值重置计数器寄存器,并在达到0时将计数器重置为该值。

  //- start down_counter
  val cntReg = RegInit(N)

  cntReg := cntReg - 1.U
  when(cntReg === 0.U) {
    cntReg := N
  }
  //- end

当我们编码和使用更多的计数器时,我们可以定义一个带有参数的函数来为我们生成一个计数器。

  def genCounter(n: Int) = {
    val cntReg = RegInit(0.U(8.W))
    cntReg := Mux(cntReg === n.U, 0.U, cntReg + 1.U)
    cntReg
  }

函数genCounter的最后一个语句是函数的返回值,在本例中是计数寄存器cntReg。

Generating Timing with Counters

除了计数事件,计数器通常用于产生时间的概念,即分频。

移位寄存器

移位寄存器是按顺序连接的触发器的集合。寄存器(触发器)的每个输出端连接到下一个寄存器的输入端。图6.12显示了一个4级移位寄存器。该电路在每个时钟节拍上将数据从左向右移位。在这种简单的形式中,电路实现了从din到dout的4抽头延迟。

这个简单移位寄存器的Chisel代码:(1)创建4位寄存器shiftReg,(2)将移位寄存器的低3位与输入din连接起来,用于寄存器的下一个输入,以及(3)使用寄存器的最高有效位(MSB)作为输出dout。
在这里插入图片描述
移位寄存器通常用于将串行数据转换为并行数据或将并行数据转换为串行数据。第11.2节显示了使用移位寄存器实现接收和发送功能的串行端口。

  val shiftReg = Reg(UInt(4.W))
  shiftReg := shiftReg(2, 0) ## din   //##按位相连
  val dout = shiftReg(3)

具有并行输出的移位寄存器

移位寄存器的串行输入并行输出配置将串行输入流转换为并行字。这可以在串行端口(UART)中用于接收功能。图6.13显示了一个4位移位寄存器,其中每个触发器输出连接到一个输出位。在4个时钟周期后,该电路将4位串行数据字转换为q中可用的4位并行数据字。在本例中,我们假设第0位(最低有效位)首先发送,因此当我们想要读取整个字时,它将到达最后一个阶段。
在这里插入图片描述
图6.13显示了一个具有并行输出功能的4位移位寄存器。

并行加载移位寄存器

在这里插入图片描述
图6.14显示了一个具有并行加载功能的4位移位寄存器。

内存

存储器可以由寄存器的集合构建,在Chisel中为Vec的Reg。然而,这在硬件上是昂贵的,并且更大的存储器结构被构建为SRAM。对于ASIC,存储器编译器构造存储器。FPGA包含片上存储器块,也称为块RAM。这些片上存储器块可以组合成更大的存储器。FPGA中的存储器通常具有一个读取端口和一个写入端口,或者可以在运行时在读取和写入之间切换的两个端口。

FPGA(以及ASIC)通常支持同步存储器。同步存储器在其输入上具有寄存器(读和写地址、写数据和写使能)。这意味着在设置地址后的一个时钟周期内读取数据可用。

图6.15显示了这种同步存储器的原理图。存储器是双端口的,具有一个读端口和一个写端口。读端口有一个输入,即读地址(rdAddr),和一个输出,即读数据(rdData)。写端口有三个输入:地址(wrAddr)、要写入的数据(wrData)和写入使能(wrEna)。注意,对于所有输入,存储器中有一个寄存器显示同步行为。
在这里插入图片描述
为了支持片上存储器,Chisel提供了存储器构造函数SyncReadMem。清单6.2显示了一个Memory组件,它实现了1KiB的内存,具有字节范围的输入和输出数据以及写使能。

class Memory() extends Module {
  val io = IO(new Bundle {
    val rdAddr = Input(UInt(10.W))
    val rdData = Output(UInt(8.W))
    val wrAddr = Input(UInt(10.W))
    val wrData = Input(UInt(8.W))
    val wrEna = Input(Bool())
  })

  val mem = SyncReadMem(1024, UInt(8.W))

  io.rdData := mem.read(io.rdAddr)

  when(io.wrEna) {
    mem.write(io.wrAddr, io.wrData)
  }
}

一个有趣的问题是,当在同一个时钟周期中,当同时读写一个地址的数据时,读出的数据会发生什么。有三种可能性:新写入的值、旧值或undefined(可能是旧值中的一些位和一些新写入的数据的混合)。FPGA中的哪种可能性取决于FPGA类型,有时可以指定。Chisel文档读取的数据是未定义的。

如果我们想读出新写入的值,我们可以构建一个转发电路,它检测到地址相等并转发写入数据。图6.16显示了带有转发电路的存储器。读取和写入地址与写入使能进行比较和门控,以在写入数据或存储器读取数据的转发路径之间进行选择。写入数据通过寄存器延迟一个时钟周期。

在这里插入图片描述

<think>嗯,用户想了解Chisel中的cat函数或操作符的用法。首先,我需要回忆一下Chisel的基本知识。Chisel是基于Scala的硬件构造语言,所以它的操作符通常和Scala的特性结合紧密。 记得在Chisel中,位操作是很常见的,比如连接不同的信号线。Cat函数应该就是用来连接多个信号的。可能类似于Verilog中的{}操作符。比如把两个信号a和b拼接在一起,高位是a,低位是b。 不过具体的函数名可能需要注意大小写,Chisel的函数通常是首字母大写的?比如Cat而不是cat。这可能是一个容易出错的地方。用户可能写的是小写的cat,但实际是Cat,所以需要明确这一点。 接下来需要找例子。比如,假设有两个UInt类型的信号a和b,使用Cat(a, b)来拼接它们。例如,a是8位,b是8位,结果就是16位。这样的例子比较典型。 另外,Cat的参数可能有多个,不仅仅是两个,比如可以连接三个或更多的信号。比如Cat(a, b, c)会把a放在最高位,接着是b,然后是c,依次连接。 可能需要提到如何导入Cat所在的包。通常,Chisel的Cat位于chisel3.util包中,或者可能需要导入chisel3._。需要确认正确的导入语句,避免用户使用时找不到。 还要注意数据类型的兼容性,比如Cat只能用于UInt、SInt等位类型的数据,不能用于其他类型。否则会报错。 此外,可能有一些应用场景,比如在构建移位寄存器、地址拼接、或者数据总线的组合时,Cat函数非常有用。举个例子,当需要将高位和低位部分分开处理后再合并时,Cat就很方便。 可能需要比较Cat和其他的位操作函数,比如Bits类的##操作符。在Chisel中,可能有两种方式拼接信号:使用Cat函数或者使用##操作符。例如,a ## b 可能等同于Cat(a, b)。需要确认这两种方法是否存在,以及它们的区别。 另外,可能需要注意位序的问题,Chisel中的位序通常是高位在前,低位在后,所以Cat(a, b)会将a放在高位,b放在低位,这与Verilog一致。 可能还需要提到Cat在生成Verilog代码时的表现,比如会被翻译成什么样的Verilog结构,是否是连续的{}拼接操作符。 最后,要检查是否有相关的文档或参考资料,比如Chisel的官方文档或例子,确保提供的信息准确。同时,引用已有的站内引用,比如用户提供的引用[1],说明Chisel的背景,增强回答的可信度。 总结一下,回答的结构应该是先解释Cat的作用,然后给出导入方式,再举例说明,包括简单例子和可能的多参数例子,提及与##操作符的关系,适用的场景,以及注意事项,比如数据类型和位序。这样用户能全面了解Cat函数的用法。</think>在Chisel硬件描述语言中,`Cat`是用于位级信号连接的核心操作符,其功能类似于Verilog中的拼接运算符`{}`。以下从语法到应用场景的分步说明: ### 1. 基础语法与导入 `Cat`位于`chisel3.util`包中,使用前需导入: ```scala import chisel3.util.Cat ``` 若未导入,可通过完整路径调用`chisel3.util.Cat` ### 2. 位拼接原理 `Cat`将输入信号按参数顺序从高位到低位连接,公式表示为: $$ \text{output} = [a_{n-1},...,a_0, b_{m-1},...,b_0] $$ 其中输入信号$a$宽度为$n$,$b$宽度为$m$,输出位宽为$n+m$ ### 3. 典型用例 #### (1) 基础连接 ```scala val a = 0x3.U(2.W) // 二进制11 val b = 0x1.U(2.W) // 二进制01 val result = Cat(a, b) // 输出位宽4,值0b1101(13) ``` 对应的Verilog代码将生成: ```verilog assign result = {a, b}; // 4位拼接结果 ``` #### (2) 多信号连接 支持任意数量参数: ```scala Cat(wireA, wireB, wireC) // 位宽总和=wireA.width+wireB.width+wireC.width ``` #### (3) 与##操作符等价 `a ## b`语法糖等价于`Cat(a,b)`: ```scala val concat = 0x1.U(4.W) ## 0x2.U(4.W) // 生成0x12(8位) ``` ### 4. 应用场景示例 #### 地址生成器: ```scala val page_num = 0x1F.U(5.W) val offset = 0x3FF.U(10.W) val full_address = Cat(page_num, offset) // 15位地址 ``` #### 数据包组装: ```scala val header = 0xA.U(4.W) val payload = 0x1234.U(16.W) val checksum = 0x5.U(4.W) val packet = Cat(header, payload, checksum) // 24位数据包 ``` ### 5. 注意事项 - **类型限制**:仅支持`UInt`/`SInt`类型,对`Bool`需显式转换: ```scala Cat(flag.asUInt, data) // 将Bool转为1位UInt后拼接 ``` - **位序验证**:建议添加断言检查位宽: ```scala assert(result.getWidth == a.getWidth + b.getWidth) ``` - **不可逆操作**:拼接后的信号无法直接拆分,需用`split`或位提取: ```scala val (upper, lower) = result.splitAt(8) // 拆分16位信号为两个8位 ``` Chisel通过这类硬件原语操作,显著提升了硬件模的抽象层次[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值