Verilog中+:和 -:
在 Verilog 中,+: 和 -: 是向量位选择的运算符,也常被称为索引的切片运算符。它们用于从较宽的向量中方便地选择一个固定宽度的部分。
这两个运算符非常有用,可以让你用更简洁、更可读的方式描述硬件的位切片操作。
-
当需要根据一个变量起始点来选取一个固定宽度的向量时,
+:和-:是唯一简洁且可综合的解决方案。 -
它们极大地简化了代码,避免了冗长的
case或if-else语句。 -
选择使用
+:还是-:取决于你的思维模式和数据的组织方式,两者在功能上是等价的,只要调整base即可。
一、基本语法和含义
signal[<starting_index> +: <width>] signal[<starting_index> -: <width>]
-
signal: 要被选择的源向量。 -
<starting_index>: 起始点的索引。这必须是一个常量或常数表达式,因为它在综合时需要是固定的。 -
<width>: 要选择的位的宽度。这也必须是一个常量。 -
+:: 从starting_index开始,向更高的索引方向选择width个位。 -
-:: 从starting_index开始,向更低的索引方向选择width个位。
关键点: 无论向量是如何声明的([7:0] 还是 [0:7]),+: 总是朝向索引增加的方向,-: 总是朝向索引减少的方向。
二、示例
1. +: (向上位选)
从 base_index 开始,向更高的位索引方向选择 width 位。
signal[base_index +: width] 等价于 signal[base_index + width - 1 : base_index]
示例:
wire [7:0] data = 8'b1011_0110;// 从索引 3 开始,向上选 4 位 wire [3:0] slice1 = data[3 +: 4]; // 结果是 data[6:3] -> 4'b1011 // 计算: 起始位 = 3, 结束位 = 3 + 4 - 1 = 6 // 所以 slice1 = data[6:3] = 4'b1011// 从索引 0 开始,向上选 4 位 wire [3:0] slice2 = data[0 +: 4]; // 结果是 data[3:0] -> 4'b0110
2. -: (向下位选)
从 base_index 开始,向更低的位索引方向选择 width 位。
signal[base_index -: width] 等价于 signal[base_index : base_index - width + 1]
示例:
wire [7:0] data = 8'b1011_0110;// 从索引 5 开始,向下选 4 位 wire [3:0] slice3 = data[5 -: 4]; // 结果是 data[5:2] -> 4'b1101 // 计算: 起始位 = 5, 结束位 = 5 - 4 + 1 = 2 // 所以 slice3 = data[5:2] = 4'b1101// 从索引 7 开始,向下选 4 位 wire [3:0] slice4 = data[7 -: 4]; // 结果是 data[7:4] -> 4'b1011
3. 示例对比
用不同的 base 和 width 来更清楚地理解:
wire [7:0] my_vector = 8'b1010_1101; // 索引: 7 6 5 4 3 2 1 0 // 值: 1 0 1 0 1 1 0 1// 选取 my_vector[5:2] assign bits1 = my_vector[5 -: 4]; // 从5向下4位:5,4,3,2 -> 4‘b1011 assign bits2 = my_vector[2 +: 4]; // 从2向上4位:2,3,4,5 -> 4’b1011// 选取 my_vector[6:4] assign bits3 = my_vector[6 -: 3]; // 从6向下3位:6,5,4 -> 3‘b101 assign bits4 = my_vector[4 +: 3]; // 从4向上3位:4,5,6 -> 3’b101// 选取 my_vector[7] (单比特) assign bit5 = my_vector[7 -: 1]; // 从7向下1位:7 -> 1‘b1 assign bit6 = my_vector[7 +: 1]; // 从7向上1位:7 -> 1’b1 (单比特时结果相同)
三、为什么使用 +: 和 -:? 优势所在
1. 与可变基地址一起使用(最重要的优势)
这是它们最强大的功能。当你想从一个可变的位置开始,提取一个固定宽度的数据段时,传统方法 [msb:lsb] 无法直接实现,而 +:/-: 可以。
场景: 从一个 32 位字中,根据一个变量 byte_sel 来选择一个字节。
reg [31:0] data; reg [1:0] byte_sel; // 0, 1, 2, 3 用于选择4个字节 reg [7:0] selected_byte;// 传统方法:需要使用 case 语句或复杂的函数,非常繁琐 always @(*) begincase (byte_sel)2‘b00: selected_byte = data[7:0];2’b01: selected_byte = data[15:8];2‘b10: selected_byte = data[23:16];2’b11: selected_byte = data[31:24];endcase end// 使用 +: 运算符:一行代码即可解决! always @(*) beginselected_byte = data[byte_sel * 8 +: 8]; end // 当 byte_sel = 0: data[0*8 +: 8] -> data[0 +: 8] -> data[7:0] // 当 byte_sel = 1: data[1*8 +: 8] -> data[8 +: 8] -> data[15:8] // 当 byte_sel = 2: data[2*8 +: 8] -> data[16 +: 8] -> data[23:16] // 当 byte_sel = 3: data[3*8 +: 8] -> data[24 +: 8] -> data[31:24]
2. 提高代码可读性和可维护性
当你的位选逻辑具有“从位置 X 开始,取 N 位”的语义时,使用 +:/-: 比写出确切的 [MSB:LSB] 更能直观地表达你的意图。
// 意图:从第 12 位开始,取一个 4 位的字段 wire [3:0] field_a = packet[12 +: 4]; // 清晰:"从12开始,向上取4位"// 不如以下方式直观: wire [3:0] field_a = packet[15:12]; // 需要心算 15=12+4-1
四、总结
| 特性 | +: (向上选取) | -: (向下选取) |
|---|---|---|
| 语法 | base +: width | base -: width |
| 方向 | 从 base 向更高位索引 | 从 base 向更低位索引 |
| 范围 | [base + width - 1 : base] | [base : base - width + 1] |
width | 必须是常量 | 必须是常量 |
base | 可以是变量或表达式 | 可以是变量或表达式 |
| 主要用途 | 用变量基地址选择固定宽度的数据段 | 用变量基地址选择固定宽度的数据段 |
+: 和 -: 是 Verilog 中非常强大的位选运算符,它们通过将固定宽度与可变起始点相结合,极大地简化了对向量进行参数化切片的工作。在处理总线、协议转换、字节序转换以及任何需要可变索引访问固定宽度子字段的场景中,它们都是不可或缺的工具。
五、关键要点与注意事项
-
宽度必须是常量:
width必须是整数常量、参数或宏定义。它不能是reg或wire类型的变量。// 正确 localparam W = 4; wire [3:0] slice = data[index +: W];// 错误!Width 不能是变量 reg [2:0] dynamic_width; wire slice_err = data[index +: dynamic_width]; // 编译错误!
-
基地址可以是变量:
base_index可以是reg或wire类型,这赋予了它极大的灵活性。 -
索引顺序:
-
+:从基地址向高位走。 -
-:从基地址向低位走。
-
-
可综合性: 这些运算符是可综合的。综合工具会将其转换为相应的多路选择器或布线逻辑。
