verilog>>>与>>
在 Verilog 代码里,>>> 是一种 运算符,叫做 算术右移(arithmetic right shift)。
1. 基本含义
>>>用于对一个 有符号数(signed) 做右移。- 它会在左边填充 符号位(即最高位 bit),保持数值的正负不变。
对比:
>>:逻辑右移(无符号右移),左边补 0>>>:算术右移(有符号右移),左边补符号位
2. 举例
假设我们有一个 8 位的有符号数:
正数情况
reg signed [7:0] a = 8'b0000_1000; // 十进制 +8
b = a >>> 2;
// b = 0000_0010 (十进制 +2)
结果等于除以 2²。
负数情况
reg signed [7:0] a = 8'b1111_1000; // 十进制 -8
b = a >>> 2;
// b = 1111_1110 (十进制 -2)
注意高位补的是 1,保持了符号。
3. 常见应用
- 缩放运算:比如你代码里出现的
rd_data2 >>> DDS_scale,本质上是把数据除以2^DDS_scale,相当于缩小幅度。 - 定点数处理:算术右移比除法运算更高效,综合到 FPGA 后资源消耗更小,延迟更低。
- 信号处理:在滤波器、NCO、AGC 等模块里常用来做移位缩放。
👉 简单总结:
>>:逻辑右移,左边补 0>>>:算术右移,左边补符号位(保证正负数方向正确)
8 位示例(便于看清位形)
-128 的 8 位二补码:1000_0000
-128 >>> 1 = 1100_0000(-64)
-128 >>> 2 = 1110_0000(-32)
-128 >>> 3 = 1111_0000(-16)
看到左边不断“补 1”,但数值其实是在变小(绝对值变小):-128 → -64 → -32 → -16。
-121 >>> 1 = 1100_0011(-61)
-121 >>> 2 = 1110_0001(-31)
-121 >>> 3 = 1111_0000(-16)
对应数学上 -121 / 8 = -15.125,向下取整是 -16。没有“突然变成 -128”这种事。
只有非常特殊的值会“保持不变”:比如 -1(1111_1111)右移任何位数都还是 -1,因为全是 1。
那需要这个情况摘出来吗?
不需要单独摘出来处理。
>>> 的算术右移天然就会把最高位(符号位/最高位 bit)往左填充,所以像 -1(全 1)无论右移多少位,结果依然是全 1,也就是 -1。综合和仿真里都会这样实现,不会出 bug,也不需要写 if 分支去特判。
不过有两点小建议写文档/写码时顺带说明一下更稳妥:
-
明确被移位的操作数是 signed(或用
$signed(x) >>> k)。
这样可以避免不同工具/读者对“符号扩展”的误解。 -
提醒“算术右移等价于按 2^k 做向下取整的除法”。
例如-3 >>> 1 = -2(而不是 -1)。很多人容易以为是“向零取整”。
如果你是在写开发文档,可以加一句备注即可,比如:
采用算术右移
>>>做缩放;对负数为符号扩展,等价于除以 2^k 并向下取整。特别地,-1右移任意位仍为-1。
那如果正弦函数幅度缩放,那1111_1111不是没有被缩放吗?
不会“没被缩放”,这是算术右移的取整方式在作祟。
-
用
>>> k缩放相当于做 除以 2^k 且向下取整(floor)。
所以:+1 >>> 1 = 0(+0.5 向下取整为 0)-1 >>> 1 = -1(-0.5 向下取整为 -1,不是 0)
这就造成一个近零的负向偏置:凡是绝对值小于 2k2^k2k 的负数,右移后都成 -1;而同幅度的正数会变成 0。正弦在过零附近就会有一点点不对称,但通常很小(1 LSB 级别)。
如果你不想出现 “-1 粘住” 的现象,有三种常用做法(任选其一):
1) 取整朝零(RTZ)
让负数右移前加上偏置 (2k−1)(2^k-1)(2k−1),相当于除法向零取整:
// x 必须是 signed
wire signed [W-1:0] x;
wire [31:0] k = DDS_scale;
// Round Toward Zero
wire signed [W-1:0] y_rtz =
(x >= 0) ? (x >>> k)
: ((x + ( (1<<k) - 1 )) >>> k);
效果:-1 >>>1 由 -1 变成 0;正负对称。
2) 就地“最接近”取整(Round-to-Nearest,半距远离 0)
// Round to nearest (ties away from zero)
wire signed [W-1:0] y_rn =
(x >= 0) ? ((x + (1<<(k-1))) >>> k)
: ((x - (1<<(k-1))) >>> k);
效果:更接近真实缩放值;-1/2 会变成 -1,+1/2 变成 +1。
3) 乘法尺度 + 移位(可带饱和)
当缩放不是纯 1/2^k 或你想顺便做饱和保护时:
// y = x * num / 2^k (num < 2^k)
// 可在相加前后加偏置实现任意取整策略,再做饱和裁剪
wire signed [W+K-1:0] prod = x * num;
wire signed [W-1:0] y = (prod + bias) >>> k; // 再加饱和裁剪
额外建议
- 确保
x是 signed,或用$signed(x) >>> k。 - 若你用的是正弦 ROM,尽量避免全幅端点出现“全 1 = -1”这种孤点(例如把幅度范围设成
[-127, +127]而不是[-128, +127]),可进一步减小端点/过零处的量化偏差。 - 若链路后面有限幅模块,在缩放后做一次饱和裁剪更稳妥,避免极端情况下的溢出。
小结:-1 >>> k = -1 是因为算术右移“向下取整”。如果你需要“向零”或“就近”行为,用上面的偏置公式即可把过零附近的负数压到 0,从而得到更对称的幅度缩放。
更多推荐
所有评论(0)