两个小而实用的运算符
Verilog 有两种 {} 形式:
- 拼接:
{a, b, c},把多个信号拼成一个更宽的信号。 - 复制:
{N{pattern}},把一个模式复制 N 次。
这两个你都会经常用到。每当总线需要拆分、合并、填充或符号扩展时,答案都是这两个运算符之一。
拼接:{a, b, c}
最简单的情况是把两个信号叠成一个:
从左到右读:a 是高半部分,b 是低半部分。{} 中的第一个操作数位于最高位。结果的宽度是所有操作数宽度之和。
你可以拼接任意数量的操作数:
wire [31:0] word = {byte3, byte2, byte1, byte0};
而且操作数的宽度可以各不相同:
wire [11:0] frame = {start_bit, data_byte, parity, stop_bit};
// 1 bit 8 bits 1 bit 2 bits = 12
一条常见规则:拼接的所有操作数都必须有明确的宽度。不带宽度的字面量(写 1 而不是 1'b1)会导致错误,因为解析器不知道该分配多少位。在 {} 中始终写 1'b0、1'b1、4'd0,绝不写单独的 0 或 1。
在左侧使用拼接
在赋值的左侧使用拼接,可以把一个宽信号拆回各个部分:
这是带进位加法器的标准写法。{carry_out, sum_bits} 在赋值左侧构成一个 9 位的目标,各个位会被分配下去:最高位给 carry_out,低 8 位给 sum_bits。
左侧拼接在 assign 和过程块中都可以使用:
always @(posedge clk) begin
{high_byte, low_byte} <= incoming_word;
end
复制:{N{pattern}}
复制运算符把一个模式复制固定的次数。写法是 {N{pattern}}:先写次数,再把模式放在它自己的一对大括号里:
N 必须是常量,因为编译器在展开(elaboration)阶段就需要知道结果的宽度。模式可以是字面量、参数,或任何宽度已知的表达式。
别忘了内层的大括号。{8 1'b1} 是语法错误,{8{1'b1}} 才是正确的。外层的 {} 是运算符,内层的 {...} 包住要复制的模式。
组合使用:符号扩展与零扩展
这两个运算符可以很自然地组合起来构建更宽的值:
{ {8{a_negative[7]}}, a_negative } 是标准的符号扩展写法。读作:把符号位复制八次,再接上原来的 8 位。最终结果是同一个数的 16 位二进制补码表示。
对于无符号的扩宽,可以依赖赋值本身:当目标更宽时,Verilog 会自动做零扩展:
wire [15:0] zext_auto = a_unsigned; // works, top 8 bits are 0
但除非操作数和目标都声明为 signed,否则符号扩展永远不会隐式发生。显式的复制写法更安全。
实用写法
构建 N 个 1 的掩码
parameter N = 5;
wire [31:0] mask = { {32-N{1'b0}}, {N{1'b1}} };
// e.g. with N=5, mask = 32'h0000_001F
反转向量
Verilog 没有内置的反转操作,但你可以用拼接直接写出来:
wire [3:0] forward = 4'b1010;
wire [3:0] reversed = {forward[0], forward[1], forward[2], forward[3]};
// reversed = 4'b0101
对于更宽的向量,用 generate 循环或 function 会更简洁。
把标志位打包成状态字节
wire [7:0] status = {
error_flag, // [7]
overflow, // [6]
underflow, // [5]
ready, // [4]
busy, // [3]
1'b0, // [2] reserved
1'b0, // [1] reserved
valid // [0]
};
1 位的保留位都写明了宽度:在 {} 中绝不写单独的 0。
常见错误
{} 中使用不带宽度的字面量。 {a, 0} 要么是语法错误,要么得到一个 32 位宽的零。务必写明宽度:{a, 1'b0}。
复制时忘了内层大括号。 {8 1'b1} 无法解析,{8{1'b1}} 才可以。
弄错顺序。 {a, b} 把 a 放在高位,b 放在低位。如果你的假设反了,某处的字节顺序就会颠倒。
复制零次。 标准 Verilog 中 {0{...}} 是非法的。SystemVerilog 允许这样写(得到一个零宽度的结果),但普通 Verilog 会拒绝它。
接下来
到这里,你已经见过所有 Verilog 运算符了。下一章换个方向,深入讲解结构:模块之间如何连接、端口的规则,以及通过实例化子模块构建更大设计的语法。
常见问题
在 Verilog 中如何拼接信号?
使用大括号:{a, b, c} 把操作数从左到右拼接成一个更宽的向量。a 的最高位成为结果的最高位,c 的最低位成为结果的最低位。结果的宽度是所有操作数宽度之和。拼接既可以用在赋值的右侧(构建一个值),也可以用在左侧(拆分一个值)。
Verilog 中的 {N{pattern}} 是什么意思?
这是复制运算符:它把 pattern 复制 N 份并拼接在一起。{8{1'b1}} 是一个 8 位全 1 的值(8'hFF)。{4{2'b10}} 是 8 位的值 8'b10101010。有了复制运算符,零扩展、符号扩展或构建任何重复的位模式都不需要手工逐位写出。
在 Verilog 中如何对信号做符号扩展?
用复制运算符重复符号位:{ {24{a[7]}}, a } 把第 7 位(符号位)复制 24 次,再接上原值,从而把 8 位的 a 扩展到 32 位。对于无符号的零扩展,可以复制 1'b0,或者在目标更宽时直接让赋值隐式完成。
可以在赋值的左侧使用拼接吗?
可以,这就是从一个宽信号源同时给多个目标赋值的方法。{carry, sum} = a + b; 在一条语句中把加法结果放进 carry(最高位)和 sum(其余位)。每个目标保持自己的宽度,解析器会相应地分配右侧的各个位。