前言
本文讲解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大家注意到,这里就不一样了,使用移位操作符>>以及<<的方式得到的结果和我们预期的不一样,或者说好像没有生效。
但事实是为什么呢? 可以查查操作符的优先级,特别是移位操作符和算术运算符: 可见,是运算符搞鬼。 那么我们需要定一个原则,就是不要记忆优先级,优先级使用括号体现出来。 例如: 上面的代码改为:
//经过简单移位得到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