FPGA的设计艺术(25)移位的陷阱

前言

本文讲解RTL语言中的移位,看起来平淡无奇的移位,为何大题小做花费一篇文章的篇幅去讲解?

这不是大题小做,移位在逻辑设计中是十分重要的角色,可以说地位相当的高,合理的利用会让你的设计更加的节约资源,处理更加的方便,当然,另外一面是不合理的使用会让你的设计失败,不起作用?

明明我移位了,为什么效果不符合预期?

算术移位与逻辑移位的区别是什么?

为什么移位可以代替某些乘除法操作?

等等诸多问题,我们会体现在文章中。

移位的方式

说起移位,大家肯定都知道如下操作符:

//算术移位 //>>> <<< reg signed [7:0] din; integer i; initial begin //逻辑左移 $display(“算术左移”); din = 8b1011_0101; $display(“Original Din = h%h or b%0b”, din, din); for(i = 0; i < 8; i = i + 1) begin $display(“din <<< %0d = b%b”, i, din<<<i); end //逻辑移位 //>> << reg [11:0] address; always@(*) begin case(address) 000>>2: begin //… end 004>>2: begin //… end //… endcase end //对于变量的移位 //左移 reg [8:0] a; always@(posedge clk) begin a <= {a[7:0], 1b0}; end //右移 reg [8:0] b; always@(posedge clk) begin b <= {1b0, b[8:1]}; end //循环左移 reg [7:0] c; always@(posedge clk ) begin c <= {c[7:0],c[8]}; end //循环右移 reg [7:0] d; always@(posedge clk) begin d <= {d[0], d[8:1]}; end
<

以上通过伪代码的方式随手举了几个例子,但也基本就这么多了,最常见的就是这些,下面我们分别认识下。

算术移位与逻辑移位

逻辑移位操作符 : << and >> 算术移位操作符 : <<< and >>>

这两者放在一起对比,才能看出区别: 给出如下测试例子:

module exam_shift( ); reg signed [7:0] din; integer i; initial begin //逻辑左移 $display(“逻辑左移”); din = 8b1011_0101; $display(“Original Din = h%h or b%0b”, din, din); for(i = 0; i < 8; i = i + 1) begin $display(“din << %0d = b%b”, i, din<<i); end //逻辑右移 $display(“逻辑右移”); din = 8b1011_0101; $display(“Original Din = h%h or b%0b”, din, din); for(i = 0; i < 8; i = i + 1) begin $display(“din >> %0d = 8b%0b”, i, din>>i); end //算术左移 $display(“算术左移”); din = 8b1011_0101; $display(“Original Din = h%h or b%0b”, din, din); for(i = 0; i < 8; i = i + 1) begin $display(“din <<< %0d = b%0b”, i, din<<<i); end //算术右移 $display(“逻辑右移”); din = 8b1011_0101; $display(“Original Din = h%h or b%0b”, din, din); for(i = 0; i < 8; i = i + 1) begin $display(“din >>> %0d = b%0b”, i, din>>>i); end end endmodule
<

定义了一个有符号的寄存器变量din,对其进行逻辑以及算术移位,结果如下:

逻辑左移 Original Din = hb5 or b10110101 din << 0 = b10110101 din << 1 = b01101010 din << 2 = b11010100 din << 3 = b10101000 din << 4 = b01010000 din << 5 = b10100000 din << 6 = b01000000 din << 7 = b10000000 逻辑右移 Original Din = hb5 or b10110101 din >> 0 = 8b10110101 din >> 1 = 8b01011010 din >> 2 = 8b00101101 din >> 3 = 8b00010110 din >> 4 = 8b00001011 din >> 5 = 8b00000101 din >> 6 = 8b00000010 din >> 7 = 8b00000001 算术左移 Original Din = hb5 or b10110101 din <<< 0 = b10110101 din <<< 1 = b1101010 din <<< 2 = b11010100 din <<< 3 = b10101000 din <<< 4 = b1010000 din <<< 5 = b10100000 din <<< 6 = b1000000 din <<< 7 = b10000000 逻辑右移 Original Din = hb5 or b10110101 din >>> 0 = b10110101 din >>> 1 = b11011010 din >>> 2 = b11101101 din >>> 3 = b11110110 din >>> 4 = b11111011 din >>> 5 = b11111101 din >>> 6 = b11111110 din >>> 7 = b11111111
<

可以很明显的看出区别,对于有符号数而言, 逻辑移位,是往移位的方向补0,算术移位是补符号位。

我为什么强调是有符号数,因为如果不显示的定义有符号数,会默认为无符号数,那么算术移位和逻辑移位没什么区别: 例如:

module exam_shift( ); // reg signed [7:0] din; reg [7:0] din; integer i; initial begin //逻辑左移 $display(“逻辑左移”); din = 8b1011_0101; $display(“Original Din = h%h or b%0b”, din, din); for(i = 0; i < 8; i = i + 1) begin $display(“din << %0d = b%b”, i, din<<i); end //逻辑右移 $display(“逻辑右移”); din = 8b1011_0101; $display(“Original Din = h%h or b%0b”, din, din); for(i = 0; i < 8; i = i + 1) begin $display(“din >> %0d = 8b%0b”, i, din>>i); end //算术左移 $display(“算术左移”); din = 8b1011_0101; $display(“Original Din = h%h or b%0b”, din, din); for(i = 0; i < 8; i = i + 1) begin $display(“din <<< %0d = b%0b”, i, din<<<i); end //算术右移 $display(“逻辑右移”); din = 8b1011_0101; $display(“Original Din = h%h or b%0b”, din, din); for(i = 0; i < 8; i = i + 1) begin $display(“din >>> %0d = b%0b”, i, din>>>i); end end endmodule
<

结果如下:

逻辑左移 Original Din = hb5 or b10110101 din << 0 = b10110101 din << 1 = b01101010 din << 2 = b11010100 din << 3 = b10101000 din << 4 = b01010000 din << 5 = b10100000 din << 6 = b01000000 din << 7 = b10000000 逻辑右移 Original Din = hb5 or b10110101 din >> 0 = 8b10110101 din >> 1 = 8b1011010 din >> 2 = 8b101101 din >> 3 = 8b10110 din >> 4 = 8b1011 din >> 5 = 8b101 din >> 6 = 8b10 din >> 7 = 8b1 算术左移 Original Din = hb5 or b10110101 din <<< 0 = b10110101 din <<< 1 = b1101010 din <<< 2 = b11010100 din <<< 3 = b10101000 din <<< 4 = b1010000 din <<< 5 = b10100000 din <<< 6 = b1000000 din <<< 7 = b10000000 逻辑右移 Original Din = hb5 or b10110101 din >>> 0 = b10110101 din >>> 1 = b1011010 din >>> 2 = b101101 din >>> 3 = b10110 din >>> 4 = b1011 din >>> 5 = b101 din >>> 6 = b10 din >>> 7 = b1
<

没有任何区别。

拼接方式移位

所谓拼接方式移位就是通过拼接操作符来手动实现移位,这种方式是我们逻辑设计中十分推荐的一种方式:

//左移 reg [8:0] a; always@(posedge clk) begin a <= {a[7:0], 1b0}; end //右移 reg [8:0] b; always@(posedge clk) begin b <= {1b0, b[8:1]}; end

为什么呢? 也是我在实践中遇到的过一种情况,使用逻辑移位操作符,在某些情况下会出现不符合我预期的情况,今天就复现下: 首先给出符合预期的情况: 给出伪代码,大家在自己测试的时候,需要加上模块名:

reg clk; reg rst; reg [7:0] a; reg [7:0] b; reg [7:0] c; //给a赋值 always@(posedge clk or posedge rst) begin if(rst) begin a <= 8b0111_1011; end else begin a <= 8b1011_0100; end end //时钟产生 initial begin clk = 0; forever begin #3 clk = ~clk; end end //经过简单移位得到b和c,进行对比 always@(posedge clk or posedge rst) begin if(rst) begin b <= d0; c <= d0; end else begin b <= a <<2 ; c <= {a[5:0],2b00} ; // b <= a <<2 + a >>2; // c <= {a[5:0],2b00} + {2b00, a[7:2]}; end end //该初始化块用于对比二者的值 initial begin // a = 8b1011_0001; rst = 1; #1 $display(“simulation time is %t”, $time); $display(“a = h%h or b%b”, a, a); #100 rst = 0; #5 $display(“simulation time is %t”, $time); $display(“a = h%h or b%b”, a, a); #5 $display(“simulation time is %t”, $time); $display(“b = a <<2 = h%h or b%b”, b, b); $display(“c = {a[5:0],2b00} = h%h or b%b”, c, c); end
<

仿真结果:

Time resolution is 1 ps simulation time is 1000 a = h7b or b01111011 simulation time is 106000 a = hb4 or b10110100 simulation time is 111000 b = a <<2 = hec or b11101100 c = {a[5:0],2b00} = hec or b11101100

可见仿真结果是一样的,无论哪种移位方式。 下面给出一种情况:

reg clk; reg rst; reg [7:0] a; reg [7:0] b; reg [7:0] c; //给a赋值 always@(posedge clk or posedge rst) begin if(rst) begin a <= 8b0111_1011; end else begin a <= 8b1011_0100; end end //时钟产生 initial begin clk = 0; forever begin #3 clk = ~clk; end end //经过简单移位得到b和c,进行对比 always@(posedge clk or posedge rst) begin if(rst) begin b <= d0; c <= d0; end else begin // b <= a <<2 ; // c <= {a[5:0],2b00} ; b <= a <<2 + a >>2; // b <= (a <<2) + (a >>2); c <= {a[5:0],2b00} + {2b00, a[7:2]}; end end //该初始化块用于对比二者的值 initial begin // a = 8b1011_0001; rst = 1; #1 $display(“simulation time is %t”, $time); $display(“a = h%h or b%b”, a, a); #100 rst = 0; #5 $display(“simulation time is %t”, $time); $display(“a = h%h or b%b”, a, a); #5 $display(“simulation time is %t”, $time); $display(“a = h%h or b%b”, a, a); $display(“a <<2 = h%h or b%b”, a<<2, a<<2); $display(“a >>2 = h%h or b%b”, a>>2, a>>2); $display(“b = a <<2 + a >>2 = h%h or b%b”, b, b); $display(“{a[5:0],2b00} = h%h or b%b”, {a[5:0],2b00}, {a[5:0],2b00}); $display(“{2b00, a[7:2]} = h%h or b%b”, {2b00, a[7:2]}, {2b00, a[7:2]}); $display(“c = {a[5:0],2b00} + {2b00, a[7:2]} = h%h or b%b”, c, c); end
<

仿真结果为:

Time resolution is 1 ps simulation time is 1000 a = h7b or b01111011 simulation time is 106000 a = hb4 or b10110100 simulation time is 111000 a = hb4 or b10110100 a <<2 = hd0 or b11010000 a >>2 = h2d or b00101101 b = a <<2 + a >>2 = h00 or b00000000 {a[5:0],2b00} = hd0 or b11010000 {2b00, a[7:2]} = h2d or b00101101 c = {a[5:0],2b00} + {2b00, a[7:2]} = h0a or b00001010

大家注意到,这里就不一样了,使用移位操作符>>以及<<的方式得到的结果和我们预期的不一样,或者说好像没有生效。

但事实是为什么呢? 可以查查操作符的优先级,特别是移位操作符和算术运算符: FPGA的设计艺术(25)移位的陷阱 可见,是运算符搞鬼。 那么我们需要定一个原则,就是不要记忆优先级,优先级使用括号体现出来。 例如: 上面的代码改为:

//经过简单移位得到b和c,进行对比 always@(posedge clk or posedge rst) begin if(rst) begin b <= d0; c <= d0; end else begin // b <= a <<2 ; // c <= {a[5:0],2b00} ; // b <= a <<2 + a >>2; b <= (a <<2) + (a >>2); c <= {a[5:0],2b00} + {2b00, a[7:2]}; end end

就得到正确的结果:

Time resolution is 1 ps simulation time is 1000 a = h7b or b01111011 simulation time is 106000 a = hb4 or b10110100 simulation time is 111000 a = hb4 or b10110100 a <<2 = hd0 or b11010000 a >>2 = h2d or b00101101 b = a <<2 + a >>2 = h0a or b00001010 {a[5:0],2b00} = hd0 or b11010000 {2b00, a[7:2]} = h2d or b00101101 c = {a[5:0],2b00} + {2b00, a[7:2]} = h0a or b00001010

移位的陷阱

从刚才所聊的内容可以看出处处都是陷阱,例如算术移位与逻辑移位的区别在于被移位的数据是否为有符号数; 对于有符号数与无符号数,左移是没有任何区别的。 关于有符号数以及无符号数的更多内容,我们会在下一篇文章认真讲解。

还有移位操作符的优先级导致的结果错误,我们还为此总结了,一定使用括号作为优先级的体现,而非去记忆优先级。

逻辑设计是给人看的,可读性很重要,逻辑本身就很底层,理解本身不易,何必用优先级去恶心人呢?

可读性,任重而道远,且行且珍惜。

移位与乘除法

移位与乘除法的关系很简单,我们常常使用的是: 左移一位,等于乘以2; 右移一位,等于除以2; 注意这里的移位指的是逻辑移位。

但限制是不要溢出。

举个例子:

reg [7:0] a; reg signed [7:0] b; initial begin a = 8b01000000; $display(“a = d%d”, a); $display(“a << 1 = d%d”, a << 1); b = 8b00101000; $display(“b = d%d”, b); $display(“b << 1 = d%d”, b << 1); a = 8b10000000; $display(“a = d%d”, a); $display(“a << 1 = d%d”, a << 1); b = 8b01001000; $display(“b = d%d”, b); $display(“b << 1 = d%d”, b << 1); a = 8b10000000; $display(“a = d%d”, a); $display(“a >> 1 = d%d”, a >> 1); b = 8b10010000; $display(“b = d%d”, b); $display(“b >> 1 = d%d”, b >> 1); end
<

仿真结果:

Time resolution is 1 ps a = d 64 a << 1 = d128 b = d 40 b << 1 = d 80 a = d128 a << 1 = d 0 b = d 72 b << 1 = d-112 a = d128 a >> 1 = d 64 b = d-112 b >> 1 = d 72

上面定义了一个有符号数b和一个无符号数a,二者进行一系列移位的结果; 可见,对于无符号数而言,只要最高位不为1,左移就会有乘以2的效果,右移有除以2的效果。

如果对于有符号数,由于使用的是逻辑运算符,故移位的手段也是逻辑移位,不管你的符号位; 对于正数,与无符号数没有区别。 对于负数,左移或右移就把符号位移掉了,因此不适用这种说法。

我们在使用这个简便规则的时候,一般场景就是无符号数,且不会溢出的场景。 其他严格的场景,使用正经的算术逻辑吧。

免责声明:文章内容来自互联网,本站不对其真实性负责,也不承担任何法律责任,如有侵权等情况,请与本站联系删除。
转载请注明出处:FPGA的设计艺术(25)移位的陷阱 https://www.yhzz.com.cn/a/12526.html

上一篇 2023-05-07 06:42:17
下一篇 2023-05-07 08:03:14

相关推荐

联系云恒

在线留言: 我要留言
客服热线:400-600-0310
工作时间:周一至周六,08:30-17:30,节假日休息。