在 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 分支去特判。

不过有两点小建议写文档/写码时顺带说明一下更稳妥:

  1. 明确被移位的操作数是 signed(或用 $signed(x) >>> k)。
    这样可以避免不同工具/读者对“符号扩展”的误解。

  2. 提醒“算术右移等价于按 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,从而得到更对称的幅度缩放。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐