代码之家  ›  专栏  ›  技术社区  ›  Sam Hammamy

如何在位运算中选择正确的左移位?

  •  1
  • Sam Hammamy  · 技术社区  · 6 年前

    我正在学习c++中的裸机编程,它通常涉及将32位硬件寄存器地址的一部分设置为某种组合。

    例如,对于IO引脚,我可以将32位地址中的第15位到第17位设置为 001 将引脚标记为输出引脚。

    我见过这样做的代码,根据另一个代码的解释,我对它有一半的理解 SO question.

    # here ra is a physical address
    # the 15th to 17th bits are being
    # cleared by AND-ing it with a value that is one everywhere 
    # except in the 15th to 17th bits
    ra&=~(7<<12);
    

    另一个例子是:

    # this clears the 21st to 23rd bits of another address
    ra&=~(7<<21);
    

    我如何选择 7. 我该如何选择要左移的位数?

    我试过这个 蟒蛇 看看我能不能弄明白

    bin((7<<21)).lstrip('-0b').zfill(32)
    '00000000111000000000000000000000'
    # this has 8, 9 and 10 as the bits which is wrong
    
    3 回复  |  直到 6 年前
        1
  •  2
  •   Pzc    6 年前

    选择7(以10为基数),因为其二进制表示为111(以2为基数的7)。

    至于为什么设置8、9和10位,是因为你从错误的方向读取。二进制,就像正常的基数10一样,从右到左计数。

    (我将此作为评论,但声誉不够高。)

        2
  •  1
  •   old_timer    6 年前

    如果您想隔离和更改寄存器中的一些位,但不是理解诸如and和or以及xor之类的逐位操作所需的全部,并且不在单个位列上操作,则每个操作数的位3用于确定结果的位3,不涉及其他位。所以我有一些二进制位,用字母表示,因为它们可以是1或0

    jklmnopq
    

       jklmnopq
    &  01110001
    ============
       0klm000q
    

    任何与一相关的事物都是一,任何与零相关的事物都是它本身。

       jklmnopq
    |  01110001
    ============
       j111nop1
    

    因此,如果要隔离并更改此变量/寄存器中的两个位,即位5和位6,并将其更改为0b10(十进制中的2),常用方法是将其与零相加,然后将其与所需值相加

       76543210
    
       jklmnopq
    &  10011111
    ============
       j00mnopq
    
       jklmnopq
    |  01000000
    ============
       j10mnopq
    

    在c中

    x = read_register(blah);
    x = (x&(~(3<<5)))|(2<<5);
    write_register(blah,x);
    

    让我们深入研究一下(3<<5)

    00000011
    00000110 1
    00001100 2
    00011000 3
    00110000 4
    01100000 5
    
    76543210
    

    这将两个一放在我们感兴趣的位的顶部,但与该值anding将隔离位,并将其他位弄乱,因此为了将这些位归零,而不弄乱寄存器中的其他位,我们需要反转这些位

    使用x=~ x将这些位反转为逻辑非运算。

    01100000
    10011111
    

    现在我们有了想要的掩码,寄存器如上所示,将有问题的位归零,而其他位则保持j00mnopq

    现在我们需要准备or(2<<5)的位

    00000010
    00000100 1
    00001000 2
    00010000 3
    00100000 4
    01000000 5
    

    给出我们想要的位模式,或者给出我们写回寄存器的j10mnopq。又是j、m、n。。。位是位,它们要么是一,要么是零,我们不想改变它们,所以我们做了额外的掩蔽和移位工作。您可能/有时会看到简单写入寄存器的示例(blah,2<<5);要么因为他们知道其他位的状态,知道他们没有使用这些其他位,零是可以/需要的,要么不知道他们在做什么。

    x read_register(blah); //bits are jklmnopq
    x = (x&(~(3<<5)))|(2<<5);
    
    z = 3
    z = z << 5
    z = ~z
    x = x & z
    z = 2
    z = z << 5
    x = x | z
    
    
    z = 3
    
    z = 00000011
    
    z = z << 5
    
    z = 01100000
    
    z = ~z
    
    z = 10011111
    
    x = x & z
    
    x = j00mnopq
    
    z = 2
    
    z = 00000010
    
    z = z << 5
    
    z = 01000000
    
    x = x | z
    
    x = j10mnopq
    

    如果有一个3位字段,则二进制为0b111,十进制为数字7或十六进制0x7。一个4位字段0b1111,它是十进制15或十六进制0xF,当您超过7时,更容易使用十六进制IMO.6位字段0x3F,7位字段0x7F等等。

    你可以在某种程度上进一步理解这一点,使其更加通用。如果有一个寄存器控制gpio引脚0到15的某些功能。从位0开始。如果您想更改gpio引脚5的属性,那么将是位10和11,5*2=10,有两个引脚,因此10和下一个引脚11。但一般来说,你可以:

    x = (x&(~(0x3<<(pin*2)))) | (value<<(pin*2));
    

    因为2是2的幂

    x = (x&(~(0x3<<(pin<<1)))) | (value<<(pin<<1));
    

    如果pin不能在编译时减少到特定值,编译器可能会进行优化。

    但是如果它是每个字段3位,并且字段开始与零位对齐

    x = (x&(~(0x7<<(pin*3)))) | (value<<(pin*3));
    

    编译器可能会用3乘法,但可能只是

    pinshift=(pinshift<1)| pinshift;

    得到乘以3的结果。取决于编译器和指令集。

    总的来说,这被称为读-修改-写,当你读一些东西时,修改其中的一些,然后写回(如果你修改了所有的东西,你不需要费心读和修改,你会写整个新的值)。人们会说,为了修改的目的,或者如果你想读/看看上面的两位是什么,那么屏蔽和移位通常会覆盖隔离变量中的位

    x = read_register(blah);
    x = x >> 5;
    x = x & 0x3;
    

    或先遮罩,然后移位

    x = x & (0x3<<5);
    x = x >> 5;
    

    一个半打的另一个指令集中有六个指令集,通常都是相等的,一些指令集一个可能比另一个更有效(或者可能是相等然后移位,或者移位然后and)。对于某些人来说,其中一个可能比另一个更有视觉意义。

    虽然从技术上讲,这是一个端值问题,因为某些处理器的位0是最重要的位。在C AFAIK中,位0是最低有效位。如果/当手册显示从左到右排列的位时,您希望您的左右移位与之匹配,因此如上所述,我显示了76543210以指示记录的位,并将其与jklmnopq关联,这是重要的从左到右信息,以继续关于修改位5和6的对话。一些文档将使用verilog或vhdl风格的符号6:5(意味着包含6到5位,更有意义的是4:2意味着4,3,2位)或[6到5],更可能只看到带有方框或线条的视觉图片,向您显示哪些位是什么字段。

        3
  •  0
  •   user207421    6 年前

    如何选择7

    要清除三个相邻的位。字底部的三个相邻位为1+2+4=7。

    我该如何选择要左移的位数

    你想清除位21-23,而不是位1-3,所以你左移另一个20。

    你的两个例子都错了。要清除15-17,您需要向左移动14;要清除21-23,您需要向左移动20。

    不,不是。你数错了。