pluveto 一、编码与转换 这类函数主要负责不同数据表示之间的转换,例如整数和独热码之间的转换,格雷码转换,以及字节序调整。 1. UIntToOh 功能: 将一个 UInt 类型的整数转换为独热码 (One-Hot) Bits。 用法: UIntToOh(value: UInt, width: Int): Bits: 将 value 转换为指定 width 的独热码。例如 value 为2,则输出 ...00100 (第2位为1)。如果 width 小于等于0,则输出全0。 UIntToOh(value: UInt): Bits: 自动根据 value 的位宽 N 推断出 2^N 的独热码宽度。 UIntToOh(value: UInt, mapping: Seq[Int]): Bits: 根据提供的 mapping 序列将 value 映射到自定义的独热码位置。输出 Bits 的宽度等于 mapping 的长度。当 value 等于 mapping 中的某个值 m 时,输出独热码对应 m 在 mapping 中首次出现的位置为1。 原理简述: 对于前两种用法,其核心原理是将1左移 value 位:B(1, width bits) |<< value。 对于带 mapping 的版本,会遍历 mapping,如果 value 等于 mapping(i),则结果的第 i 位置1。 具体例子: UIntToOh(U(2), 8): 输入: value = U"010" (十进制 2), width = 8 输出: B"00000100" UIntToOh(U(1, 2 bits)): 输入: value = U"01" (十进制 1, 位宽 2 bits) 输出: B"0010" (宽度自动为 2^2 = 4) UIntToOh(U(5), Seq(7, 5, 2, 5)): 输入: value = U(5), mapping = Seq(7, 5, 2, 5) 输出: B"0100" (因为 value 5 首次出现在 mapping 的第1个索引位置) 2. UIntToOhMinusOne 功能: 将一个 UInt 类型的整数 N 转换为一种特殊的编码,其结果的低 N+1 位为1 (常被称为温度计码或填充码)。例如 value 为2,则输出 ...000111 (假设宽度足够)。 用法: UIntToOhMinusOne(value: UInt, width: Int): Bits: 将 value 转换为指定 width 的上述编码。 UIntToOhMinusOne(value: UInt): Bits: 自动根据 value 的位宽 N 推断出 2^N 的编码宽度。 原理简述: B(U(B(1, width bits) |<< value)-1)。先生成一个标准的独热码,然后减1,利用借位机制将独热位右边的所有位(包括独热位自身)置1。 具体例子: UIntToOhMinusOne(U(2), 8): 输入: value = U"010" (十进制 2), width = 8 中间独热码: B"00000100" 输出: B"00000011" (书中例子是 B(U(B(1, width bits) |<< value)-1),若为B((B(1, width bits) |<< (value+1))-1)则低 value+1 位为1,即 B"00000111")。按代码 B(U(B(1, width bits) |<< value)-1),应该是 value 2 得到 0011。 "Meaning that value 2 will give 0011 instead of 0100" 代码注释说明了这一点。所以是低 value 位为 1,如果 value 从0开始算,则是 value+1 个 1。若 value 为2,则输出 B"....011" (低2位为1,如果从0开始计数则是index 0和1为1) 按代码实现 B(U(B(1, width bits) |<< value)-1): value = U(2), width = 8: B(1, 8 bits) |<< U(2) -> B"00000100" U(B"00000100") -> U(4) U(4) - 1 -> U(3) B(U(3)) (宽度8) -> B"00000011" UIntToOhMinusOne(U(0), 4): 输入: value = U(0), width = 4 B(1,4 bits) |<< U(0) -> B"0001" U(B"0001") - 1 -> U(0) 输出: B"0000" 3. OHToUInt 功能: 将独热码 Bits 或 Seq[Bool] 转换回 UInt 整数。 用法: OHToUInt(bitVector: BitVector): UInt: 输入为 BitVector。 OHToUInt(bools: Seq[Bool]): UInt: 输入为 Seq[Bool]。 OHToUInt(bitVector: BitVector, mapping: Seq[Int]): UInt: 根据 mapping 将输入的独热码转换为对应的 UInt 值。 OHToUInt(oh: Seq[Bool], mapping: Seq[Int]): UInt: 同上,输入为 Seq[Bool]。 原理简述: 标准版本:通过构建一个真值表或逻辑网络,将独热码中为1的位的索引作为输出 UInt 的值。例如,如果输入 B"00100",第2位为1,则输出 U(2)。其内部实现对输出 UInt 的每一位 ret(retBitId) 进行计算,查看输入 bools 中所有索引的 retBitId 位为1的那些 bools(boolsBitId),将它们或起来。 映射版本:如果输入独热码的第 i 位为1,则输出为 mapping(i)。 具体例子: OHToUInt(B"00100"): 输入: B"00100" (第2位为1) 输出: U(2) (位宽为 log2Up(5) = 3 bits, 即 U"010") OHToUInt(B"0100", Seq(10, 20, 30, 40)): 输入: oh = B"0100" (第1位为1), mapping = Seq(10, 20, 30, 40) 输出: U(20) (位宽为 log2Up(40+1) bits) 4. toGray / fromGray 功能: toGray(uint: UInt): Bits: 将无符号整数 UInt 转换为格雷码 Bits。 fromGray(gray: Bits): UInt: 将格雷码 Bits 转换回无符号整数 UInt。 原理简述: toGray: gray = uint ^ (uint >> 1)。 fromGray: uint(msb) = gray(msb); uint(i) = gray(i) ^ uint(i+1) for i from width-2 down to 0。 具体例子: toGray(U(5, 4 bits)) (即 U"0101"): 输入: uint = U"0101" U"0101" ^ (U"0101" >> 1) -> U"0101" ^ U"0010" -> U"0111" 输出: B"0111" fromGray(B"0111"): 输入: gray = B"0111" ret(3) = gray(3) = B'0' ret(2) = gray(2) ^ ret(3) = B'1' ^ B'0' = B'1' ret(1) = gray(1) ^ ret(2) = B'1' ^ B'1' = B'0' ret(0) = gray(0) ^ ret(1) = B'1' ^ B'0' = B'1' 输出: U(Cat(ret(3), ret(2), ret(1), ret(0))) -> U"0101" (十进制 5) 5. EndiannessSwap 功能: 对 BitVector 类型的数据进行字节序(大小端)转换。 用法: EndiannessSwap[T <: BitVector](that: T, base: BitCount = 8 bits): T that: 输入的 BitVector。 base: 定义转换的基本单元大小,默认为8 bits (1字节)。输入的总位宽必须是 base 的整数倍。 原理简述: 将输入数据按 base 大小分块,然后颠倒这些块的顺序。例如,对于32位数据和8位 base,块 [31:24] 与块 [7:0] 交换,块 [23:16] 与块 [15:8] 交换。 具体例子: EndiannessSwap(B"0011223344556677", 16 bits) (假设输入是16进制表示的64位数据) 输入: that = Bx"0011223344556677", base = 16 bits 块0: Bx"6677" (bits 15 downto 0) 块1: Bx"4455" (bits 31 downto 16) 块2: Bx"2233" (bits 47 downto 32) 块3: Bx"0011" (bits 63 downto 48) 颠倒后顺序: 块0, 块1, 块2, 块3 -> 块3, 块2, 块1, 块0 (对于字节序,是最低地址字节和最高地址字节交换) 输出: Bx"6677445522330011" (如果按字节交换,结果为 Bx"7766554433221100")。 按代码逻辑(将输入低位块放到输出高位): 输入: that = Bx"11223344" (32 bits), base = 8 bits nbrBase = 4 i = 0: rangeIn = 7 downto 0 (Bx"44"), rangeOut = 31 downto 24. ret[31:24] := that[7:0] i = 1: rangeIn = 15 downto 8 (Bx"33"), rangeOut = 23 downto 16. ret[23:16] := that[15:8] i = 2: rangeIn = 23 downto 16 (Bx"22"), rangeOut = 15 downto 8. ret[15:8] := that[23:16] i = 3: rangeIn = 31 downto 24 (Bx"11"), rangeOut = 7 downto 0. ret[7:0] := that[31:24] 输出: Bx"44332211"
pluveto 二、选择与复用 这类函数用于根据选择信号从多个数据源中选择一个输出。 1. MuxOH / OhMux 功能: 独热码控制的多路选择器 (One-Hot Multiplexer)。OhMux 是 MuxOH 的别名。 用法: apply[T <: Data](oneHot: BitVector, inputs: Seq[T]): T (及 IndexedSeq[Bool], Vec[T] 的变体) or[T <: Data](oneHot: BitVector, inputs: Seq[T], bypassIfSingle: Boolean = false): T (及变体) 原理简述: apply 方法: 如果 oneHot 位数为2,则直接使用 oneHot(0) ? inputs(0) | inputs(1)。否则,先通过 OHToUInt(oneHot) 将独热码转换为整数索引,然后用该索引从 inputs (通常是 Vec) 中选取元素。 or 方法: 将每个 inputs(i) 与 oneHot(i) 进行“掩码”操作(通常是 oneHot(i) ? inputs(i).asBits | B(0)),然后将所有掩码后的结果进行位或(reduceBalancedTree(_ | _))。如果 bypassIfSingle 为 True 且只有一个输入,则直接返回该输入。 具体例子: MuxOH(B"0100", Vec(dataA, dataB, dataC, dataD)) 输入: oneHot = B"0100" (选择第1个索引的输入), inputs OHToUInt(B"0100") -> U(1) 输出: inputs(1) (即 dataB) MuxOH.or(B"0010", Vec(B"1111", B"0011", B"1010", B"0101")) 输入: oneHot = B"0010", inputs = Vec(B"1111", B"0011", B"1010", B"0101") 掩码操作: False ? B"1111" | B"0000" -> B"0000" False ? B"0011" | B"0000" -> B"0000" True ? B"1010" | B"0000" -> B"1010" False ? B"0101" | B"0000" -> B"0000" 位或结果: B"0000" | B"0000" | B"1010" | B"0000" 输出: B"1010" 2. PriorityMux 功能: 优先级多路选择器。根据一组选择信号 sel(Seq[Bool] 或 Bits)的优先级选择对应的输入 in。 用法: PriorityMux[T <: Data](in: Seq[(Bool, T)], msbFirst: Boolean = false): T PriorityMux[T <: Data](sel: Seq[Bool], in: Seq[T], msbFirst: Boolean = false): T PriorityMux[T <: Data](sel: Bits, in: Seq[T], msbFirst: Boolean = false): T 原理简述: 如果 msbFirst 为 false (默认),则 sel 中索引较低的信号具有较高优先级。它通过平衡树的方式实现:(sel_i, data_i)和 (sel_j, data_j) 合并为 (sel_i | sel_j, Mux(sel_i, data_i, data_j)),直到只剩一个元素。如果 msbFirst 为 true,则反转输入顺序再进行操作,使得高索引具有高优先级。 具体例子: PriorityMux(Seq(False, True, True), Seq(dataA, dataB, dataC)) 输入: sel = Seq(False, True, True), in = Seq(dataA, dataB, dataC) 优先级: sel(0) (连接dataA) > sel(1) (连接dataB) > sel(2) (连接dataC) sel(0) 为 False。sel(1) 为 True,具有最高有效优先级。 输出: dataB PriorityMux(B"101", Seq(dataX, dataY, dataZ), msbFirst = true) (即 sel = Seq(True, False, True)) 输入: sel = B"101", in = Seq(dataX, dataY, dataZ), msbFirst = true msbFirst = true 意味着 sel(2) (连接dataZ) > sel(1) (连接dataY) > sel(0) (连接dataX) sel(2) (原 B"101" 的LSB,即 sel.asBools 的 sel(0)) 为 True (如果 sel.asBools 是 Seq(B"1",B"0",B"1"),msbFirst会反转,即 dataZ的优先级最高)。 按代码 ordered = in.reverse,实际是 sel 被反转。sel 变成 Seq(sel(N-1), ..., sel(0)),其对应的 in 也是 Seq(in(N-1), ..., in(0))。 sel = B"101" -> Seq(True, False, True) msbFirst = true: 原始顺序 (sel(0), in(0)), (sel(1), in(1)), (sel(2), in(2))。 sel.asBools.zip(in).reverse -> (sel(2), in(2)), (sel(1), in(1)), (sel(0), in(0)) 即 (True, dataZ), (False, dataY), (True, dataX),优先级从左到右。 sel(2) (即 B"101" 的MSB) 为 True。 输出: dataZ
pluveto 三、位操作与运算 这类函数用于对数据进行底层的位级操作和基本算术运算。 1. Reverse 功能: 反转 BitVector 中所有位的顺序。 用法: Reverse[T <: BitVector](that: T): T 原理简述: 创建一个新的 BitVector,其 ret(i) 等于 that(that.getWidth-1-i)。 具体例子: Reverse(B"10110") 输入: that = B"10110" 输出: B"01101" 2. Min / Max 功能: Min.list[T <: Data with Num[T]](nums: Seq[T]): T: 计算一系列数值中的最小值。 Max.list[T <: Data with Num[T]](nums: Seq[T]): T: 计算一系列数值中的最大值。 用法: Min(num1, num2, ...) Max(num1, num2, ...) 原理简述: 使用平衡树结构(reduceBalancedTree)递归地两两比较,直到找到最终的最小/最大值。 具体例子: Min(U(5), U(2), U(8), U(3)) 输入: Seq(U(5), U(2), U(8), U(3)) 输出: U(2) Max(S(-1), S(-5), S(0)) 输入: Seq(S(-1), S(-5), S(0)) 输出: S(0) 3. AddWithCarry 功能: 执行两个 UInt 数的加法,并返回和以及进位标志。 用法: AddWithCarry(left: UInt, right: UInt): (UInt, Bool) 原理简述: 将两个输入数扩展一位(高位补0),然后相加。结果的MSB即为进位,其余位为和。 具体例子: AddWithCarry(U"1101", U"0101") (假设都是4位) 输入: left = U(13), right = U(5) left.resize(5) -> U"01101", right.resize(5) -> U"00101" temp = U"01101" + U"00101" -> U"10010" 和: temp.resize(4) -> U"0010" (即 U(2)) 进位: temp.msb -> True (因为 U"10010" 的MSB是1) 输出: (U(2, 4 bits), True) (13 + 5 = 18, 18 mod 16 = 2, carry = 1) 4. SetFromFirstOne 功能: 从输入 that (视为 Bits) 的最低有效位(LSB)开始,找到第一个为'1'的位,并将结果中从该位到最高有效位(MSB)的所有位置为'1'。如果输入全为0,则输出全为0。 用法: SetFromFirstOne[T <: Data](that: T, firstOrder: Int = LutInputs.get): T 原理简述: 这是一个基于查找表(LUT)的优化实现,通过分层或操作来构建结果。tmp(i) := build(i,1),build(target, order) 会检查 input(target) 是否为1,或者 input(target) 左边的位是否有1(通过递归或缓存的 cache(offset to target))。 具体例子: SetFromFirstOne(B"0010100") 输入: that = B"0010100" 第一个 '1' 在位2 (从LSB, 0-indexed)。 输出: B"1111100" (位2到MSB都设为1) SetFromFirstOne(B"0000000") 输入: that = B"0000000" 输出: B"0000000" 5. Napot (Next Available Power Of Two mask / Set From First Zero) 功能: 从输入 that (视为 Bits) 的LSB开始,找到第一个为'0'的位,并将结果中从该位的下一位到MSB的所有位置为'1'。可以理解为 SetFromFirstOne(~that) << 1。 用法: Napot(that: Bits, firstOrder: Int = LutInputs.get): Bits 原理简述: 利用 SetFromFirstOne。先对输入按位取反 (~that),然后应用 SetFromFirstOne,最后将结果左移一位。 具体例子: Napot(B"1110110") (宽度7) 输入: that = B"1110110" ~that -> B"0001001" SetFromFirstOne(B"0001001") (第一个1在bit 0) -> B"1111111" B"1111111" << 1 (宽度应为 widthOf(that)+1,但这里返回的是 Bits 类型,其结果位宽与 SetFromFirstOne 相同,左移会引入0) -> 应该是 SetFromFirstOne(~that, firstOrder) << 1,结果位宽是widthOf(that) + 1 bits Let's re-check the description "Bits(widthOf(that + 1 bits) which work as a mask which will bet set after the lowest index in which that contains a bit 0". Example that = 1111 => 00000 ~B"1111" -> B"0000". SetFromFirstOne(B"0000") -> B"0000". B"0000" << 1 -> B"00000" (assuming output is 5 bits). Correct. Example that = 0111 => 10000 ~B"0111" -> B"1000". SetFromFirstOne(B"1000") (bit 3 is 1) -> B"1000". B"1000" << 1 -> B"10000". Correct. Example that = B"11011" (width 5) ~that -> B"00100" SetFromFirstOne(B"00100") (first '1' at index 2) -> B"11100" (B"11100" << 1) (output width 6) -> B"111000" 输出: B"111000" (第3位开始为1,因为原输入第2位是第一个0) 6. PropagateOnes 功能: toLsb[T <: BitVector](that: T): T: 从MSB向LSB传播'1'。如果 that(i) 为'1',则结果中 ret(i) 到 ret(0) 均为'1'。实际上是 ret(i) := that.dropLow(i).orR,意味着如果 that 中从 i 到MSB有任何'1',则 ret(i) 为'1'。 toMsb[T <: BitVector](that: T): T: 从LSB向MSB传播'1'。如果 that(i) 为'1',则结果中 ret(i) 到 ret(MSB) 均为'1'。通过 toLsb(that.reversed).reversed 实现。 用法: PropagateOnes.toLsb(bits) PropagateOnes.toMsb(bits) 具体例子: PropagateOnes.toLsb(B"0100100") 输入: B"0100100" ret(6): that.dropLow(6).orR -> B"0".orR -> False ret(5): that.dropLow(5).orR -> B"01".orR -> True ret(4): that.dropLow(4).orR -> B"010".orR -> True ret(3): that.dropLow(3).orR -> B"0100".orR -> True ret(2): that.dropLow(2).orR -> B"01001".orR -> True ret(1): that.dropLow(1).orR -> B"010010".orR -> True ret(0): that.dropLow(0).orR -> B"0100100".orR -> True 输出: B"0111111" PropagateOnes.toMsb(B"0100100") 输入: B"0100100" that.reversed -> B"0010010" PropagateOnes.toLsb(B"0010010") -> B"0011111" B"0011111".reversed -> B"1111100" 输出: B"1111100" 7. Shift 功能: rightWithScrap(that: Bits, by: UInt): Bits: 对 that 右移 by 位,并将移出的位(如果有任何'1'移出)累积到结果的LSB。 原理简述: 这是一个可变位移,迭代地处理 by 的每一位。如果 by(i) 为真,表示需要进行 2^i 位的移位。logic 右移 2^i 位,同时检查在这次移位中是否有'1'被移出 (logic(0, 1 << i bits) =/= 0),如果有,则 scrap 设为 True。最终结果是移位后的 logic 或上 scrap。 具体例子: Shift.rightWithScrap(B"10110", U(2)) 输入: that = B"10110", by = U(2) (即 U"10") logic = B"10110", scrap = False i = 0: by(0) is False. logic 不变. i = 1: by(1) is True. scrap setWhen(by(1) && logic(0, 2 bits) =/= 0) -> scrap setWhen(True && B"10" =/= 0) -> scrap = True. logic = B"10110" |>> 2 -> B"00101" 输出: logic | scrap.asBits.resized -> B"00101" | B"1" (假设resized为1位) -> B"00101" (如果 scrap 是单个 Bool 并转换为1位 Bits,则 B"1"。B"00101" | B"00001" -> B"00101") Let's re-trace carefully: logic is the shifted value, scrap is a single Bool. that = B"10110", by = U(2) logic_init = B"10110", scrap_init = False i=0 (by(0)=0): no change to logic, scrap. i=1 (by(1)=1): logic(0, 1<<1 bits) is logic(0, 2 bits) which is B"10". This is =/= 0. So scrap becomes True. logic becomes logic |>> (1<<1) which is B"10110" |>> 2 -> B"00101". Final: logic | scrap.asBits.resized -> B"00101" | B"1".asBits.resized(1) -> B"00101" | B"1" If resized matches logic width: B"00101" | B"00001" -> B"00101". The intent is scrap ORs into the LSB if any bits were shifted out. This usually means (shifted_value & ~1) | scrap_bit. The code is logic | scrap.asBits.resized. If scrap is true, it ORs a 1 into LSB. So, B"00101" is correct as scrap became True and B"00101" already has LSB as 1. Consider Shift.rightWithScrap(B"00110", U(1)): logic = B"00110", by = U(1) (U"1") i=0 (by(0)=1): logic(0, 1<<0 bits) is logic(0,1 bit) which is B"0". scrap remains False. logic becomes logic |>> 1 -> B"00011". Final: B"00011" | False.asBits.resized -> B"00011". Consider Shift.rightWithScrap(B"00101", U(1)): logic = B"00101", by = U(1) (U"1") i=0 (by(0)=1): logic(0, 1 bit) is B"1". scrap becomes True. logic becomes logic |>> 1 -> B"00010". Final: B"00010" | True.asBits.resized -> B"00010" | B"1" -> B"00011". This interpretation seems correct. The "scrap bit" (if any 1s were shifted out) is OR-ed into the LSB of the shifted result. 8. OH (One-Hot Utilities) 功能: isLegal(that: Bits): Bool: 检查一个 Bits 向量是否是合法的独热码(即最多只有一位是1)或全0。 原理简述: isLegal 生成所有可能的单位独热码模式 (1<<e) 和全0模式,然后检查输入 that 是否等于其中之一。 具体例子: OH.isLegal(B"00100") -> True OH.isLegal(B"00000") -> True OH.isLegal(B"00110") -> False 9. OHMasking (One-Hot Masking) 功能: 提供多种从一个位向量中提取单个'1'(通常是LSB或MSB)并形成独热码,或进行轮询仲裁的函数。 用法与原理: first[T <: Data](that: T): T: 返回一个独热码,只有 that 中最低有效位的'1'被保留。原理:input & ~(input - 1)。 last[T <: Data](that: T): T: 返回一个独热码,只有 that 中最高有效位的'1'被保留。原理:Reverse(first(Reverse(that)))。 firstV2[T <: Data](that: T, firstOrder: Int = LutInputs.get): T: first 的另一种实现,可能基于LUT优化。 lastV2[T <: Data](that: T, firstOrder: Int = LutInputs.get): T: last 的另一种LUT优化实现。 roundRobin[T <: Data](requests: T, ohPriority: T): T: 轮询仲裁。ohPriority 是一个独热码,指示当前哪个请求拥有最高优先级(它之后请求的优先级依次降低)。 原理: 将请求信号加倍 uRequests @@ uRequests。利用 ohPriority (表示上一个授权) 构造一个掩码 ~(doubleRequests - uGranted) 来找到从 uGranted 之后第一个有效的请求。 roundRobinMasked[T <: Data, T2 <: Data](requests: T, priority: T2): Bits: 带掩码的轮询。priority 是一个 width-1 位的掩码,用于动态调整请求的优先级。 roundRobinMaskedInvert[T <: Data, T2 <: Data](requests: T, priority: T2): Bits: priority 逻辑反转的 roundRobinMasked。 roundRobinMaskedFull[T <: Data, T2 <: Data](requests: T, priority: T2): Bits: priority 与 requests 等宽的 roundRobinMasked。 roundRobinNext[T <: Data](requests: T, next: Bool): Bits: 带有状态的轮询,next 信号用于更新优先级到下一个。 具体例子: OHMasking.first(B"0110100"): 输入: B"0110100" input = U(52), input - 1 = U(51) -> B"0110011" ~(input - 1) -> B"1001100" input & ~(input - 1) -> B"0110100" & B"1001100" -> B"0000100" 输出: B"0000100" OHMasking.roundRobin(B"10110", B"00100") (requests, last_granted_oh) uRequests = U"10110" (22), uGranted = U"00100" (4) doubleRequests = U"1011010110" doubleRequests - uGranted = U"1011010110" - U"00100" = U"1011010010" ~(doubleRequests - uGranted) = ~(U"1011010010") (mask) doubleGrant = doubleRequests & mask This gets complex, but effectively it finds the first request bit set in requests starting cyclically after the bit set in ohPriority. If requests = B"10110" (R4,R2,R1 set) and ohPriority = B"00100" (P2 was last), search order R1, R0, R4, R3. R1 is set. 输出: B"00010" (R1 gets grant)
pluveto 四、计数与统计 这些函数用于统计位向量中'1'的个数,或找到特定位的位置等。 1. CountOne 功能: 计算输入 (Seq[Bool] 或 BitVector) 中'1'的个数。 用法: CountOne(thats: BitVector): UInt CountOne(thats: Seq[Bool]): UInt 原理简述: 采用分治策略,将输入分组(例如每3或4位一组),用LUT计算每组中'1'的个数,然后将各组的计数值用平衡树加法器相加。 具体例子: CountOne(B"1011001") 输入: B"1011001" (包含4个'1') 输出: U(4) (位宽为 log2Up(input_width + 1) bits) 2. CountOneOnEach (Currently seems to be a TODO for full implementation) 功能: 对于输入 thats: Seq[Bool],计算 thats.take(1) 中'1'的个数, thats.take(2) 中'1'的个数, ..., 直到 thats.take(N) 中'1'的个数。返回一个 Seq[UInt]。 用法: CountOneOnEach(thats: Seq[Bool]): Seq[UInt] 原理简述: (当前代码中的注释表明 //TODO,简单实现是循环调用 CountOne) 具体例子: CountOneOnEach(Seq(True, False, True, True)) 输入: Seq(T, F, T, T) CountOne(Seq(T)) -> U(1) CountOne(Seq(T,F)) -> U(1) CountOne(Seq(T,F,T)) -> U(2) CountOne(Seq(T,F,T,T)) -> U(3) 输出: Seq(U(1), U(1), U(2), U(3)) 3. CountLeadingZeroes / CountTrailingZeroes 功能: CountLeadingZeroes(in: Bits): UInt: 计算从MSB开始的连续'0'的个数。 CountTrailingZeroes(in: Bits): UInt: 计算从LSB开始的连续'0'的个数。 原理简述: CountLeadingZeroes: 采用分治递归方法。如果位宽不是2的幂,先填充0使其成为2的幂。然后将输入对半分,计算左半部分(MSB侧)的CLZ (clzL) 和右半部分的CLZ (clzR)。如果左半部分全0 (即 clzL == w/2),则总CLZ为 w/2 + clzR;否则总CLZ为 clzL。 CountTrailingZeroes: 通过 CountLeadingZeroes(in.reversed) 实现。 具体例子: CountLeadingZeroes(B"00010110") 输入: B"00010110" (8 bits) 输出: U(3) CountTrailingZeroes(B"01101000") 输入: B"01101000" (8 bits) in.reversed -> B"00010110" CountLeadingZeroes(B"00010110") -> U(3) 输出: U(3) 4. LeastSignificantBitSet 功能: 找到输入 (Seq[Bool] 或 Bits) 中最右边(LSB侧)'1'的索引。 用法: LeastSignificantBitSet(thats: Bits): UInt list(thats: Seq[Bool]): UInt 原理简述: 从MSB向LSB遍历输入,当遇到第一个'1'时,将其索引赋值给结果 ret。由于是从高位向低位遍历并赋值,最后 ret 中保存的就是最低位'1'的索引。 具体例子: LeastSignificantBitSet(B"0010100") (7 bits) 输入: B"0010100" LSB '1' 在索引2。 输出: U(2) (位宽 log2Up(7+1)=3 bits) 5. SetCount / ClearCount 功能: SetCount(in: Iterable[Bool]): UInt 或 SetCount(in: Bits): UInt: 计算输入中'1'的个数(同 CountOne,但实现方式不同)。 ClearCount(in: Iterable[Bool]): UInt 或 ClearCount(in: Bits): UInt: 计算输入中'0'的个数。 原理简述: SetCount: 采用递归的分治方法。将输入对半分,递归计算两部分的 SetCount,然后相加。 ClearCount: 通过 SetCount(~in) 实现。 具体例子: SetCount(B"10110") -> U(3) ClearCount(B"10110") (即 SetCount(B"01001")) -> U(2) 6. MajorityVote 功能: 对输入的 BitVector 或 Seq[Bool] 进行多数表决。如果'1'的个数超过总位数的一半,则输出 True,否则输出 False。 用法: MajorityVote(that: BitVector): Bool MajorityVote(that: collection.IndexedSeq[Bool]): Bool 原理简述: 确定触发阈值 trigger = size / 2 + 1。然后遍历所有可能的组合,如果某个组合中'1'的个数等于 trigger,并且这些位在输入中都为'1' (通过 localAnd 实现),则全局或 globalOr 置 True。这是一种组合逻辑实现,对于较多输入位可能较复杂。 具体例子: MajorityVote(B"10110") (5 bits) 输入: B"10110" (3个'1') size = 5, trigger = 5/2 + 1 = 3. 由于有3个'1',等于 trigger。 输出: True MajorityVote(B"10010") (5 bits) 输入: B"10010" (2个'1') 2 < 3. 输出: False
pluveto 五、时序逻辑与计数器 这些工具用于构建状态机、计数器、延迟单元等时序电路。 1. Counter 功能: 创建一个可配置的向上计数器。 用法: Counter(start: BigInt, end: BigInt): Counter: 指定起始值和结束值(包含)。 Counter(range: Range): Counter: 使用 Range 对象指定计数范围。 Counter(stateCount: BigInt): Counter: 计数范围 [0, stateCount-1]。 Counter(bitCount: BitCount): Counter: 计数范围 [0, 2^bitCount-1]。 以上均可附加 inc: Bool 参数,使能计数器递增。 成员与方法: value: UInt: 当前计数值 (只读)。 willIncrement: Bool: 内部信号,设为 True 使计数器在下一个周期递增。 willClear: Bool: 内部信号,设为 True 使计数器在下一个周期清零 (复位到 start)。 increment(): 调用此方法使 willIncrement := True。 clear(): 调用此方法使 willClear := True。 willOverflow: Bool: 当计数器达到 end 且 willIncrement 为 True 时,此信号为 True。 load(value: UInt): 加载新值。 init(initValue: BigInt): 设置计数器初始值。 原理简述: 内部有一个寄存器 value 存储当前计数值,valueNext 存储下一个周期的值。根据 willIncrement 和 willClear 更新 valueNext。当 value 达到 end 且要递增时,valueNext 回到 start。 具体例子: val myCounter = Counter(0, 9) // 计数范围 0 到 9 when(io.enable) { myCounter.increment() } when(myCounter.willOverflow) { // 当从9跳到0时 io.pulse := True } io.countOut := myCounter.value 若 start=0, end=9, value 从0开始。enable 为高时,value 递增。当 value 为9且 enable 为高时,下一周期 value 变为0,willOverflow 为高。 2. CounterFreeRun 功能: 创建一个自由运行(始终递增)的计数器。 用法: CounterFreeRun(stateCount: BigInt): 计数范围 [0, stateCount-1]。 CounterFreeRun(bitCount: BitCount): 计数范围 [0, 2^bitCount-1]。 原理简述: 基于 Counter 实现,但其 willIncrement 信号被强制设为 True。 具体例子: val freeCounter = CounterFreeRun(100) // 计数范围 0 到 99,每个周期自动加1 when(freeCounter.willOverflow){ // 每100个周期执行一次 } 3. CounterUpDown 功能: 创建一个可向上或向下计数的计数器。 用法: CounterUpDown(stateCount: BigInt, handleOverflow: Boolean = true): 计数范围 [0, stateCount-1]。 CounterUpDown(stateCount: BigInt, incWhen: Bool, decWhen: Bool, handleOverflow: Boolean = true): 根据 incWhen 和 decWhen 控制增减。 成员与方法: value: UInt: 当前计数值。 increment(): 使能向上计数。 decrement(): 使能向下计数。 willOverflow: 向上溢出。 willUnderflow: 向下溢出。 init(initValue: BigInt): 设置初始值。 原理简述: 类似 Counter,但有两个控制信号 incrementIt 和 decrementIt。根据这两个信号决定是加1、减1还是保持不变。handleOverflow 控制是否在溢出/下溢时回绕。注意:当 stateCount 不是2的幂且 handleOverflow 为 true 时,当前实现会 assert(false)。 具体例子: val upDownCounter = CounterUpDown(16) // 0 到 15 when(io.goUp && !io.goDown) { upDownCounter.increment() } when(io.goDown && !io.goUp) { upDownCounter.decrement() } 4. CounterMultiRequest 功能: 创建一个计数器,可以响应多个并发的、自定义的更新请求。 用法: CounterMultiRequest(width: Int, requests: (Bool, UInt => UInt)*): UInt width: 计数器的位宽。 requests: 一个可变参数列表,每个元素是一个元组 (condition: Bool, updateFunction: UInt => UInt)。如果 condition 为真,则应用 updateFunction 来更新计数值。 原理简述: 内部有一个寄存器 counter。在每个周期,counterNext 初始化为 counter 的当前值。然后按顺序检查 requests:如果某个请求的 condition 满足,就用其 updateFunction 更新 counterNext。最后 counter := counterNext。注意,请求的应用是有顺序的。 具体例子: val myVal = CounterMultiRequest(8, (io.loadEnable, oldVal => io.loadValue), // 最高优先级 (io.incrementEnable, oldVal => oldVal + 1), (io.decrementEnable, oldVal => oldVal - 1) ) 如果 io.loadEnable 为真,计数器加载 io.loadValue。否则,如果 io.incrementEnable 为真,计数器加1。再否则,如果 io.decrementEnable 为真,计数器减1。 5. Timeout 功能: 实现一个超时定时器。当达到指定的周期数后,输出一个标志位。 用法: Timeout(cycles: BigInt): 指定超时周期数。 Timeout(time: TimeNumber): 指定超时时间 (如 5 ms),会自动转换为周期数。 Timeout(frequency: HertzNumber): 指定频率,转换为时间周期后再创建。 成员与方法: value: Bool (隐式): 超时标志,达到限值后为 True。 limit: BigInt: 设置的超时周期数。 stateRise: Bool: 在超时发生的那个周期为 True。 clear(): 清除超时状态,复位计数器。 clearWhen(cond: Bool): 当 cond 为真时清除。 init(value: Bool): 初始化超时状态。 原理简述: 内部使用一个 CounterFreeRun 从0计数到 limit-1。当计数器溢出时,设置状态寄存器 state 为 True,同时 stateRise 在该周期变高。limit 必须大于1。 具体例子: val watchdog = Timeout(1000 cycles) // 1000个周期后超时 when(io.activity) { watchdog.clear() // "喂狗" } when(watchdog) { // 等价于 watchdog.value // 超时发生,执行复位或其他操作 } 6. Delay 功能: 将输入信号延迟指定的周期数。 用法: Delay[T <: Data](that: T, cycleCount: Int, when: Bool = null, init: T = null, onEachReg: T => Unit = null): T that: 要延迟的信号。 cycleCount: 延迟的周期数。 when: 可选的使能信号,仅当 when 为 True 时寄存器才更新。 init: 可选的寄存器初始值。 onEachReg: 可选的函数,对延迟链中的每个寄存器执行操作。 原理简述: 实例化 cycleCount 个寄存器链。RegNext(ptr, init) 或 RegNextWhen(ptr, when, init)。 具体例子: val delayedSignal = Delay(io.inputSignal, 3) io.inputSignal 的值将在3个周期后出现在 delayedSignal。 val delayedValid = Delay(io.inputValid, 2, when = io.enable, init = False) io.inputValid 被延迟2拍,但只有当 io.enable 为高时,延迟链中的寄存器才会更新。初始值为 False。 7. DelayWithInit (已合并到 Delay) 功能: 这是一个便利函数,等同于调用 Delay 并可以为延迟链中的寄存器指定初始值。在较新版本中,Delay 本身已包含 init 参数,此函数可能不再是必需的,或者只是 Delay 的一个简单包装。 用法: DelayWithInit[T <: Data](that: T, cycleCount: Int)(onEachReg: (T) => Unit = null): T (签名中没有 init,但其描述暗示了初始化。可能实际调用 Delay 并传递 that.getZero 作为 init,或者依赖 RegNext 的默认初始化行为。) 实际代码 Delay[T](that, cycleCount, onEachReg = onEachReg) 表明它不直接处理 init,而是依赖 Delay 中 RegNext 的默认行为(若 init 为 null)。 8. History 功能: 捕获一个信号在过去多个周期的历史值。 用法: History[T <: Data](that: T, length: Int, when: Bool = null, init: T = null): Vec[T] length: 历史记录的长度。返回的 Vec 中,vec(0) 是当前周期的 that,vec(1) 是上一周期的 that,依此类推,vec(length-1) 是 length-1 个周期前的 that。 History[T <: Data](that: T, range: Range, ...): Vec[T] range: 指定要捕获的历史范围,例如 2 to 5 表示捕获2到5个周期前的值。 原理简述: 构建一个移位寄存器链。Vec(0) 直接连接 that,Vec(i) 是 RegNext(Vec(i-1)) (或 RegNextWhen)。 具体例子: val pastValues = History(io.data, 4) pastValues(0) == io.data (当前) pastValues(1) == io.data (上个周期) pastValues(2) == io.data (上上个周期) pastValues(3) == io.data (上上上个周期) val specificHistory = History(io.data, 2 to 3, when = io.captureEnable, init = dataZero) specificHistory(0) 是 io.data 2个周期前的值。 specificHistory(1) 是 io.data 3个周期前的值。 仅当 io.captureEnable 为真时更新历史。 9. HistoryModifyable 功能: 一个更复杂的历史记录组件,允许在历史记录链的中间修改数据。它主要用于流 (Stream/Flow) 数据。 用法: HistoryModifyable[T <: Data](payloadType: HardType[T], depth: Int) io.input: slave(Flow[T]): 输入流。 io.outStreams: Vec[master(Stream[T])]: 从历史记录的每个阶段输出流。outStreams(0) 是延迟1拍的流,以此类推。 io.inStreams: Vec[slave(Stream[T])]: 输入流,用于修改对应阶段的历史数据。 io.willOverflow: Bool: 如果输入有效且所有历史阶段都无法接收数据,则为 True。 原理简述: 内部构建了一个 Stream 寄存器链。每个阶段的输出 (outStreams) 可以被外部消耗,同时也可以通过 inStreams 注入新的数据来修改该阶段的值。有复杂的握手逻辑来处理流的 valid/ready。 具体例子: val modHistory = HistoryModifyable(UInt(8 bits), 3) modHistory.io.input << myFlowSource // modHistory.io.outStreams(0) 是延迟1拍的流 // modHistory.io.inStreams(0).valid :=条件 // modHistory.io.inStreams(0).payload := newValue 10. DelayEvent 功能: 延迟一个事件(Bool 信号)指定的周期数或时间。当输入事件发生时,启动一个内部计数器;计数完成后,输出事件才发生。 用法: DelayEvent(event: Bool, cycle: BigInt): Bool: 延迟固定周期数。 DelayEvent(event: Bool, cycle: UInt): Bool: 延迟周期数由 UInt信号动态指定。 DelayEvent(event: Bool, t: Double, hz: Double): Bool: 延迟指定时间 t (秒),基于时钟频率 hz。 原理简述: 固定周期: 当 event 为 True 时,启动一个 Counter。当 Counter 溢出时,输出 True。 动态周期: 当 event 为 True 时,计数器 counterNext 从0开始计数,直到等于 cycle。输出在匹配时为 True。如果 event 再次发生,计数器复位。 具体例子: val delayedPulse = DelayEvent(io.inputPulse, 5) 如果 io.inputPulse 在周期 N 变高,则 delayedPulse 将在周期 N+5 变高一拍 (假设 Counter 是从0到 cycle-1,且 willOverflow 在达到 cycle-1 后准备跳回0时为真)。 根据代码 Counter(cycle),计数范围是 [0, cycle-1]。willOverflow 在计数到 cycle-1 且将要变成0时为 True。所以延迟是 cycle 个周期。 如果 event 在 T0 发生,计数器在 T0 清零,开始计数。T0->0, T1->1, ..., T(cycle-1)->(cycle-1)。在 T(cycle-1) 时,counter.willOverflow 为 True,run 仍然为 True。所以输出在 T(cycle-1) 为 True。这意味着延迟了 cycle-1 个周期后事件发生。不,counter.increment() 在前面,所以如果 cycle 是1,counter 在 event 发生时清零,然后 willOverflow 为真,所以是0周期延迟。如果 cycle 是2,event -> clear; count=0, overflow=F; next state count=1, overflow=T。所以延迟1周期。 更正:Counter(cycle) 从 0 到 cycle-1。如果 cycle=1,计数器是 Counter(1) 即 [0,0]。event 时 counter.clear() (变0),counter.willOverflow (0===0) 为 True,所以输出为 True。0周期延迟。 如果 cycle=N > 0,事件发生时,counter 清零。第0拍:value=0。第1拍:value=1... 第 N-1 拍:value=N-1,此时 willOverflow 为 True。所以输出在 N-1 拍后出现。 如果希望延迟 N 个周期,cycle 参数应为 N+1。 11. GrayCounter 功能: 创建一个格雷码计数器。 用法: GrayCounter(width: Int, enable: Bool): UInt width: 计数器的位宽。 enable: 计数使能信号。 原理简述: 格雷码的特性是相邻计数值之间只有一位发生变化。其实现通常基于一个二进制计数器,并通过特定逻辑(如异或)转换为格雷码,或者直接实现格雷码的转换规则。这里的实现比较独特:gray(i) 翻转的条件是 word(i) 为真且 found 为假,word 由 gray 的部分位和 even 标志构成。even 标志在每次使能时翻转。 具体例子: val grayVal = GrayCounter(4, io.enableCounting) // grayVal 会在 io.enableCounting 为高时,按4位格雷码序列变化 // 0000 -> 0001 -> 0011 -> 0010 -> 0110 -> ...
pluveto 六、控制流与辅助工具 这些工具提供了编程上的便利,如条件构建、可调用逻辑块等。 1. WhenBuilder 功能: 以程序化的方式构建 when/elsewhen/otherwise 链。这在循环或需要动态生成条件逻辑时非常有用。 用法: val wb = WhenBuilder() for(i <- 0 until 4){ wb.when(io.sel === i) { // do something for i } } wb.otherwise { // default case } 原理简述: WhenBuilder 内部维护一个 WhenContext。每次调用 when 或 elsewhen 都会更新这个上下文,将其连接到前一个条件块的 elsewhen 或创建一个新的 when 块。otherwise 则附加到最后一个 WhenContext。 具体例子: (见用法) 2. Callable 功能: 创建一个可被“调用”的硬件逻辑块。当调用时,其内部逻辑被激活。 用法: Callable(doIt: => Unit) doIt: 一个代码块,定义了被调用时执行的硬件操作。 返回一个对象,该对象有一个 call() 方法。 原理简述: 内部有一个 isCalled 的 Bool 型寄存器或信号(这里是 False,意味着它是一个组合逻辑的触发)。当 call() 方法被硬件逻辑调用时,isCalled 被设为 True。Callable 的主体逻辑包裹在 when(isCalled){ doIt } 中。 具体例子: val myAction = Callable { io.output := io.inputA + io.inputB printf("Action Called!\n") // 仅在仿真时 } when(io.trigger) { myAction.call() // 当 trigger 为真时,执行 Callable 内部的加法和打印 } 3. DataOr 功能: 创建一个数据端口,其值是多个源端口的位或 (Bitwise OR) 结果。这对于汇集多个可能的写入者到一个共享目标上很有用,但要注意所有源同时驱动非零值可能导致非预期结果。 用法: DataOr[T <: Data](dataType: HardType[T]) dataType: 定义了数据端口的类型。 value: T: 最终进行位或操作后的结果端口。 newPort(): T: 创建一个新的源端口,它将参与最终的位或运算。 原理简述: 内部维护一个 values 列表,存储所有通过 newPort() 创建的源端口。在组件 afterElaboration 阶段,value 被赋值为所有 values 中端口的 asBits 进行平衡树位或的结果。 具体例子: val sharedFlags = DataOr(Bits(4 bits)) val sourceA = sharedFlags.newPort() val sourceB = sharedFlags.newPort() sourceA := B"0000" sourceB := B"0000" when(io.eventA) { sourceA := B"0001" } when(io.eventB) { sourceB := B"0100" } // io.resultFlags := sharedFlags.value // 如果 eventA 和 eventB 都为真, sharedFlags.value 会是 B"0101" 4. whenMasked / whenIndexed 功能: whenMasked[T](things: TraversableOnce[T], conds: TraversableOnce[Bool] or Bits)(body: T => Unit): 对 things 集合中的每个元素 thing,如果其对应的 conds 中的条件为真,则执行 body(thing)。 whenIndexed[T](things: TraversableOnce[T], index: UInt, relaxedWidth: Boolean = false)(body: T => Unit): 根据 index 的值,选择 things 集合中对应索引的元素 thing,并对其执行 body(thing)。 原理简述: whenMasked: 遍历 things 和 conds,对每一对 (thing, cond),生成 when(cond){ body(thing) }。 whenIndexed: 生成一个 switch(index) 语句,对 things 中的每个元素,在 case 匹配其索引时执行 body(thing)。 具体例子: whenMasked(myVecOfRegs, io.enableMask) { reg => reg := 0 } 如果 myVecOfRegs 有4个元素,io.enableMask 是4位 Bits。若 io.enableMask = B"1010",则 myVecOfRegs(0) 和 myVecOfRegs(2) 会在 when 条件下被清零。 whenIndexed(myVecOfData, io.selectIndex) { dataElement => io.output := dataElement } 如果 io.selectIndex 为 U(1),则 io.output := myVecOfData(1)。
pluveto 七、分析与调试 这些工具主要用于设计后期分析和调试,如路径分析、时钟域报告等。 1. AnalysisUtils 功能: 提供一组用于分析已阐述 (elaborated) 设计的工具。 用法与方法: addOption(parser: scopt.OptionParser[Unit]): 向命令行解析器添加 --analyse-path 选项,用于指定要分析的逻辑路径的起点和终点。 report(top: Component): 根据通过命令行选项收集的路径信息,使用 PathTracer 打印逻辑路径报告。 getBaseType(path: String, c: Component): BaseType: 根据字符串路径 (如 "mySubModule/mySignal") 在组件层级中查找信号。 seekNonCombDrivers(that: BaseType)(body: Any => Unit): 查找驱动给定信号的非组合逻辑元素(如寄存器、内存)。 solveCombDriver[T <: BaseType](that: T): T: 尝试追溯组合逻辑链,找到最原始的驱动源。 foreachToplevelIoCd(top: Component)(body: (BaseType, Seq[ClockDomain]) => Unit): 遍历顶层模块的IO,并报告它们相关的时钟域。 原理简述: 这些函数通常在SpinalHDL设计阐述完成后,对生成的内部数据结构(如AST、网表)进行遍历和分析。例如,PathTracer 会在信号之间搜索所有可能的逻辑路径。 具体例子: 在生成Verilog的主程序中: val report = SpinalVerilog(MyTopLevel()) val analysis = new AnalysisUtils() analysis.report(report.toplevel) // 如果命令行提供了 --analyse-path 命令行: my-program --analyse-path from=dut/sub/regA,to=dut/sub/combB 这会打印从 regA 到 combB 的逻辑路径。 2. LatencyAnalysis 功能: 分析两个或多个信号(Expression)之间的组合逻辑或时序逻辑路径上的寄存器级数(延迟)。 用法: LatencyAnalysis(pathNode1, pathNode2, ...): Integer: 计算从 pathNode1 到 pathNode2,再到 pathNode3等的总延迟。 LatencyAnalysis.list(paths: Seq[Expression]): Integer: 同上,输入为序列。 LatencyAnalysis.impl(from: Expression, to: Expression): Integer: 计算从 from 到 to 的寄存器级数。 原理简述: impl 函数通过广度优先搜索(BFS)类似的方式,从 to 表达式反向追溯到 from 表达式。每当路径穿过一个寄存器 (BaseType 的 isReg 为 true) 或某些类型的内存端口 (MemReadSync, MemReadWrite) 时,延迟计数增加。pendingQueues 用于管理不同延迟级别的待访问节点。 具体例子: // 假设 regA -> combLogic -> regB -> regC val lat1 = LatencyAnalysis(regA, regB) // 期望输出 1 (regA 到 regB 经过1个寄存器 regB) // 或者 0 如果 regA 直接驱动 regB 的输入 // 实际是 regA 的输出到 regB 的输入路径。 // 如果 regA.valueNext 驱动 regB.valueNext,那么是0。 // 如果 regA.value 驱动 regB.valueNext, 那么是1。 // LatencyAnalysis 计算的是从 from 的输出到 to 的输入之间的寄存器数量。 // regA (output) -> someComb -> regB (input) : 0 stage // regA (output) -> regStage1 (input, output) -> regB (input) : 1 stage (regStage1) // 在SpinalHDL中, regA 是一个寄存器实例 (BaseType)。 // LatencyAnalysis(regA, regB) // path: regA.value (Expression) -> ... -> regB.valueNext (Expression) // 通常,我们会分析 regA.value 到 regB.value 之间的路径。 // 假设: val regA = Reg(UInt(8 bits)) // val regB = RegNext(regA + 1) // val regC = RegNext(regB + 1) val d1 = LatencyAnalysis(regA, regB) // 应该是1 (regB 本身) val d2 = LatencyAnalysis(regB, regC) // 应该是1 (regC 本身) val dTotal = LatencyAnalysis(regA, regB, regC) // 应该是 d1 + d2 = 2 此函数结果依赖于 from 和 to 如何连接。如果 from 是一个寄存器的输出,to 是另一个寄存器的输入,并且它们之间只有组合逻辑,则延迟为0。如果它们之间有一个或多个寄存器级,则延迟为这些寄存器的数量。 3. WrapWithReg 功能: 自动为一个组件 (Component) 的所有输入和输出端口添加两级寄存器。 用法: WrapWithReg.on(c: Component): 直接修改组件 c。 class Wrapper(c: => Component) extends Component: 创建一个包装器组件,它实例化 c 并对其IO进行寄存。 原理简述: 遍历组件的IO。对于每个输入 e,它会被替换为 e := RegNext(RegNext(in(cloneOf(e))))。对于每个输出 e,它会被替换为 out(cloneOf(e)) := RegNext(RegNext(e))。这常用于跨时钟域或为了满足时序而在模块边界添加流水线。 具体例子: class MyModule extends Component { val io = new Bundle { val dataIn = in UInt(8 bits) val dataOut = out UInt(8 bits) } io.dataOut := io.dataIn + 1 } val wrappedModule = new WrapWithReg.Wrapper(new MyModule) // wrappedModule 的 dataIn 输入到 MyModule 实例的 dataIn 之间有两级寄存器 // MyModule 实例的 dataOut 到 wrappedModule 的 dataOut 之间也有两级寄存器
pluveto 八、Pimped 类扩展 SpinalHDL 大量使用 Scala 的 "pimp my library" 模式(隐式类)来为现有类型添加额外方法,使其更易用或功能更强。 1. StringPimped 为 String 类型添加的方法: toVecOfByte(encoding: String = "UTF-8"): Vec[Bits]: 将字符串按指定编码转换为 Vec[Bits],每个 Bits 为8位宽。 toBigInt: BigInt: 将字符串(如 "0xFF" 或 "255") 转换为 BigInt。 例子: "Hello".toVecOfByte() -> Vec(B(0x48), B(0x65), B(0x6C), B(0x6C), B(0x6F)) "0x1A".toBigInt -> BigInt(26) 2. TraversableOncePimped[T <: Data] 为 Seq[T] (其中 T <: Data) 添加的方法: reduceBalancedTree(op: (T, T) => T): 使用平衡树结构执行归约操作。 asBits(): Bits: 将 Seq[Data] 中的所有元素连接成一个 Bits 向量。 read(idx: UInt): T: 将序列视为 Vec 并用 idx 读取。等价于 Vec(pimped).read(idx)。 apply(idx: UInt): T: 同 read。 sExist(condition: T => Bool): Bool: 序列中是否存在元素满足 condition (硬件)。 sContains(value: T): Bool: 序列中是否包含 value (硬件)。 sCount(condition: T => Bool): UInt: 计算满足 condition 的元素个数 (硬件)。 sFindFirst(condition: T => Bool): (Bool, UInt): 找到第一个满足 condition 的元素的有效性和索引 (硬件)。 shuffle(indexMapping: Int => Int): Vec[T]: 根据映射重排元素。 例子: val myVec = Vec(U(1,4 bits), U(2,4 bits), U(3,4 bits)) myVec.asBits() -> B"0011_0010_0001" (取决于 Cat 的顺序,通常是 Cat(vec.reverse)) myVec.sContains(U(2)) -> True (如果 myVec 中有一个元素等于 U(2)) 3. TraversableOnceBoolPimped 为 Seq[Bool] 添加的方法: orR: Bool: 所有 Bool 元素的逻辑或。 andR: Bool: 所有 Bool 元素的逻辑与。 xorR: Bool: 所有 Bool 元素的逻辑异或。 例子: Seq(True, io.sel, False).orR -> True | io.sel | False -> io.sel (如果 io.sel 是 Bool) 4. ClockDomainPimped 为 ClockDomain 添加的方法: withBufferedResetFrom(resetCd: ClockDomain, bufferDepth: Option[Int] = None): ClockDomain: 创建一个新的时钟域,其时钟与原 cd 相同,但复位信号来自于 resetCd 的复位,并经过异步复位同步释放逻辑 (通常是两级同步器,bufferDepth 可调)。 withOptionalBufferedResetFrom(cond: Boolean)(...): 如果 cond 为真,则行为同上,否则返回原 cd。 例子: val clkA = ClockDomain(io.clkA, io.resetA) val clkB = ClockDomain(io.clkB, io.resetB) // clkC 使用 clkB 的时钟,但其复位信号来源于 clkA 的复位,并经过同步处理 val clkC = clkB.withBufferedResetFrom(clkA) 5. BitAggregator (纯软件工具) 功能: 一个纯软件类,用于将不同位宽的小数据块打包成字节序列。主要用于软件驱动程序准备要发送到硬件的数据,或解析从硬件接收的数据。 用法: add(valueParam: BigInt, bitCount: Int): 添加一个数据。 add(valueParam: Boolean): 添加一个1位数据。 getWidth: Int: 获取当前已添加数据的总位宽。 toBytes: Seq[Byte]: 将所有添加的数据打包成字节序列。 clear(): 清空已添加的数据。 例子: val aggregator = new BitAggregator() aggregator.add(0xA, 4) // 4 bits aggregator.add(true) // 1 bit aggregator.add(0x1F, 5) // 5 bits // Total 10 bits: 11111_1_1010 (MSB_first_piece ... LSB_first_piece) // toBytes会将其打包: Byte0 = (0x1F << 1 | 1) << 4 | 0xA (bits 7-0 of 1111111010) // 0xFA (11111010), 0x4 (000001xx, x表示下一个字节的,这里是 11) -> 0x01 (00000011) val bytes = aggregator.toBytes // Seq[Byte] = Seq(0xFA, 0x01) (assuming LSB of value is packed first into byte bits) // value = (element._1 & (BigInt(1) << element._2) - 1) << byteBitId; // packs LSB of element into current byteBitId // 0xA (1010) -> byte(0)[3:0] = 1010 // true (1) -> byte(0)[4] = 1 // 0x1F (11111) -> byte(0)[7:5]=111, byte(1)[1:0]=11 // Byte0: 111_1_1010 = 0xFA // Byte1: xxxxxx11 = 0x03 (padding with 0) println(aggregator.toString) // "FA 03"