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

李锐博恩 2021-04-27 00:06:35 5670

前言

本文讲解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的效果。

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

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

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 98 7 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
李锐博恩
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区