FPGA的设计艺术(25)移位的陷阱
前言
本文讲解RTL语言中的移位,看起来平淡无奇的移位,为何大题小做花费一篇文章的篇幅去讲解?
这不是大题小做,移位在逻辑设计中是十分重要的角色,可以说地位相当的高,合理的利用会让你的设计更加的节约资源,处理更加的方便,当然,另外一面是不合理的使用会让你的设计失败,不起作用?
明明我移位了,为什么效果不符合预期?
算术移位与逻辑移位的区别是什么?
为什么移位可以代替某些乘除法操作?
等等诸多问题,我们会体现在文章中。
移位的方式
说起移位,大家肯定都知道如下操作符:
//算术移位
//>>> <<<
reg signed [7:0] din;
integer i;
initial begin
//逻辑左移
$display("算术左移");
din = 8'b1011_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], 1'b0};
end
//右移
reg [8:0] b;
always@(posedge clk) begin
b <= {1'b0, 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 = 8'b1011_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 = 8'b1011_0101;
$display("Original Din = 'h%h or 'b%0b", din, din);
for(i = 0; i < 8; i = i + 1) begin
$display("din >> %0d = 8'b%0b", i, din>>i);
end
//算术左移
$display("算术左移");
din = 8'b1011_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 = 8'b1011_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 = 8'b10110101
din >> 1 = 8'b01011010
din >> 2 = 8'b00101101
din >> 3 = 8'b00010110
din >> 4 = 8'b00001011
din >> 5 = 8'b00000101
din >> 6 = 8'b00000010
din >> 7 = 8'b00000001
算术左移
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 = 8'b1011_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 = 8'b1011_0101;
$display("Original Din = 'h%h or 'b%0b", din, din);
for(i = 0; i < 8; i = i + 1) begin
$display("din >> %0d = 8'b%0b", i, din>>i);
end
//算术左移
$display("算术左移");
din = 8'b1011_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 = 8'b1011_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 = 8'b10110101
din >> 1 = 8'b1011010
din >> 2 = 8'b101101
din >> 3 = 8'b10110
din >> 4 = 8'b1011
din >> 5 = 8'b101
din >> 6 = 8'b10
din >> 7 = 8'b1
算术左移
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], 1'b0};
end
//右移
reg [8:0] b;
always@(posedge clk) begin
b <= {1'b0, 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 <= 8'b0111_1011;
end
else begin
a <= 8'b1011_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],2'b00} ;
// b <= a <<2 + a >>2;
// c <= {a[5:0],2'b00} + {2'b00, a[7:2]};
end
end
//该初始化块用于对比二者的值
initial begin
// a = 8'b1011_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],2'b00} = '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],2'b00} = '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 <= 8'b0111_1011;
end
else begin
a <= 8'b1011_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],2'b00} ;
b <= a <<2 + a >>2;
// b <= (a <<2) + (a >>2);
c <= {a[5:0],2'b00} + {2'b00, a[7:2]};
end
end
//该初始化块用于对比二者的值
initial begin
// a = 8'b1011_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],2'b00} = 'h%h or 'b%b", {a[5:0],2'b00}, {a[5:0],2'b00});
$display("{2'b00, a[7:2]} = 'h%h or 'b%b", {2'b00, a[7:2]}, {2'b00, a[7:2]});
$display("c = {a[5:0],2'b00} + {2'b00, 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],2'b00} = 'hd0 or 'b11010000
{2'b00, a[7:2]} = 'h2d or 'b00101101
c = {a[5:0],2'b00} + {2'b00, 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],2'b00} ;
// b <= a <<2 + a >>2;
b <= (a <<2) + (a >>2);
c <= {a[5:0],2'b00} + {2'b00, 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],2'b00} = 'hd0 or 'b11010000
{2'b00, a[7:2]} = 'h2d or 'b00101101
c = {a[5:0],2'b00} + {2'b00, a[7:2]} = 'h0a or 'b00001010
移位的陷阱
从刚才所聊的内容可以看出处处都是陷阱,例如算术移位与逻辑移位的区别在于被移位的数据是否为有符号数;
对于有符号数与无符号数,左移是没有任何区别的。
关于有符号数以及无符号数的更多内容,我们会在下一篇文章认真讲解。
还有移位操作符的优先级导致的结果错误,我们还为此总结了,一定使用括号作为优先级的体现,而非去记忆优先级。
逻辑设计是给人看的,可读性很重要,逻辑本身就很底层,理解本身不易,何必用优先级去恶心人呢?
可读性,任重而道远,且行且珍惜。
移位与乘除法
移位与乘除法的关系很简单,我们常常使用的是:
左移一位,等于乘以2;
右移一位,等于除以2;
注意这里的移位指的是逻辑移位。
但限制是不要溢出。
举个例子:
reg [7:0] a;
reg signed [7:0] b;
initial begin
a = 8'b01000000;
$display("a = 'd%d", a);
$display("a << 1 = 'd%d", a << 1);
b = 8'b00101000;
$display("b = 'd%d", b);
$display("b << 1 = 'd%d", b << 1);
a = 8'b10000000;
$display("a = 'd%d", a);
$display("a << 1 = 'd%d", a << 1);
b = 8'b01001000;
$display("b = 'd%d", b);
$display("b << 1 = 'd%d", b << 1);
a = 8'b10000000;
$display("a = 'd%d", a);
$display("a >> 1 = 'd%d", a >> 1);
b = 8'b10010000;
$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的效果。
如果对于有符号数,由于使用的是逻辑运算符,故移位的手段也是逻辑移位,不管你的符号位;
对于正数,与无符号数没有区别。
对于负数,左移或右移就把符号位移掉了,因此不适用这种说法。
我们在使用这个简便规则的时候,一般场景就是无符号数,且不会溢出的场景。
其他严格的场景,使用正经的算术逻辑吧。
- 分享
- 举报
![易百纳技术社区](https://ebaina.oss-cn-hangzhou.aliyuncs.com/production/direct/resource/202306/14/empty.png)
-
浏览量:4855次2021-03-14 01:58:15
-
浏览量:7415次2021-02-07 00:59:28
-
浏览量:5566次2021-02-14 01:58:16
-
浏览量:3972次2021-03-12 23:39:20
-
浏览量:8749次2021-02-20 17:50:13
-
浏览量:4266次2021-06-20 20:10:03
-
浏览量:3881次2021-03-13 01:55:14
-
浏览量:4844次2021-05-23 23:50:14
-
浏览量:4386次2021-04-27 00:05:09
-
浏览量:6270次2021-03-14 02:34:44
-
浏览量:5145次2021-03-14 01:12:39
-
浏览量:7851次2020-12-27 18:30:21
-
浏览量:6642次2021-03-15 23:18:36
-
浏览量:7053次2021-04-10 01:44:48
-
浏览量:8591次2021-03-21 22:58:26
-
浏览量:11111次2021-05-23 01:13:25
-
浏览量:4466次2021-05-17 23:44:33
-
浏览量:5209次2021-03-17 23:24:53
-
浏览量:4574次2021-03-18 22:57:24
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
李锐博恩
![易百纳技术社区](/img/web/choose_active.png)
![易百纳技术社区](/img/web/choose_active.png)
![易百纳技术社区](/img/web/choose_active.png)
![易百纳技术社区](/img/web/choose_active.png)
![易百纳技术社区](/img/web/choose_active.png)
![易百纳技术社区](/img/web/paywx.png)
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明