CSc 116 notes

Previous page Back    Next Next page  Contents

7) Bit Fiddling operations

So far we have been manipulating numbers, in sizes of either bytes or words. In this section, we will look at the operations that are usually provided for working with individual bits. There are several reasons for doing this. One can compress data, using only as many bits as necessary, so several fields can share the same word. We have seen that instruction layouts divide a word up like this. An assembler must put the elements of an instruction together, for example, lining up the 5-bit fields for registers and combining them.

Multiplying and dividing by powers of 2 is also easily done simply by shifting bits. Adding a zero on the right of a binary number multiplies it by two, just as adding a zero on the right of a decimal number multiplies it by 10. A shift left instruction is handier, and often faster, than setting up a divide operation. Let's look at the bit-fiddling operations, and how they are implemented in MIPS instructions.

Shifting operations

By shifting we mean moving all the bits in a word either right or left. Think of them as books filling a shelf. If we push them all over, some books will fall off the end, and be lost. There will be space available on the other end. Since a word is always 32 bits, we need to put something in that space. In a logical shift, the empty positions are filled with 0's. If you shift a word 32 or more bits, it will contain all 0's. In a rotate operation, the bits falling out one end are replaced at the other end. (Think of picking up each book as it falls off the shelf, and replacing it at the other end.) As a result, no bits are lost, and rotating a word by 32 bit positions restores its original value.

All processors, so far as I know, have shifting hardware, and instructions, capable of shifting at least 1 bit left or right. There doesn't appear to be any reason to shift a 32 bit word by more that 31 bits, any more than one would need to set an ordinary clock 50 hours ahead. You could just set it 2 hours ahead.

Here are some examples of shift and rotate operations, each starting from the same bit pattern. I hilighted 3 bits, so you can see their new locations. The new filler 0000's are in italics

Before:             11011011011011011011011011111111
Shift right 6       00000011011011011011011011011011
Shift left  5       01101101101101101101111111100000
Rotate left 4       10110110110110110110111111111101 <-- leftmost 4 came here
So, it is possible to get any bit into any position, bringing other bits along the same distance, and either losing bits of the end, or replacing them at the other end.

Arithmetic shifts (signed numbers)

Adding a zero on the right of a binary number multiplies it by two, as I mentioned. By shifting a number 1 bit left, each bit has twice the place value it had before, and we bring in a 0 to hold the units place. Multiplication by any power of 2 is possible in this way. For instance, to multiply by 16, shift left 4 bits. Example, 7 * 16 Conversely, shifting right divides. Any remainder is thrown away with the discarded bits. 127 / 8, shifting right 3 bits: There are some special considerations for arithmetic shifts.
-38  =  11111111111111111111111111011010,  divide by 4, shift right aritnmetic 2
gives   11111111111111111111111111110110,  which is -10
Now, you may not quite agree with that answer. It makes mathematicians happy, because it agrees with the remainder of +2, that is, -10*4 +2 = -38. The answer has been truncated in the negative direction. Computer scientists are divided, and in fact some hardware gives this result for division, and some gives -9. In fact the MIPS architecture does not specificy how negative integers should be divided. As for the SPIM simulator, it simply uses the divide instruction of the "host" processor, so results of divide instructions can vary! Not so when you divide by shifting, you will always get the "mathmatical" result we have just seen.

MIPS shift instructions

The format of these instructions is: op code, Destination register, source register, shift amount. The shift amout is usually a constant, 1-31 bits make sense. It may also be contained in a register (variable shift). Examples, source is $t0:  
Type of shift Left Right
Logical sll 
sllv
srl 
srlv
Arithmetic (none, 
use sll)
sra 
srav
Rotate rol ror

Logical operations - Boolean Algebra

Today's digital computers are composed entirely of logical circuits, called gates. These gates use 1 or more transistors to implement the logical operatios AND, OR, and NOT. A variation on (inclusive) OR is eXclusive-OR, usually written XOR. These are also called boolean operations, after George Boole, originator of "The Algebra of Truth."

Boolean algebra deals with two values, FALSE and TRUE. Since boolean values can be encoded in 1 bit, we can do so, and by convention 0 represents FALSE and 1 represents TRUE. The operations can be defined by enumerating all the possible values of operands and results in a "Truth table"

operands AND  OR  XOR
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0
NOT simply reverses the value. NOT 0 = 1, and NOT 1 = 0

Some laws of boolean algebra, these identities can be verified using truth tables: (In these laws, + means OR, a bar over something means NOT, while AND is shown like multiplication in algebra, by writing two terms together.)

     A 1 = A         A + 0  =  A                   identity
     A 0 = 0         A + 1  =  1                   null
     A A = A         A + A  =  A                   idempotent
     A(A + B) = A    A + AB =  A                   absorption
       _                         _
     A A  = 0                A + A  =  1           inverse
     AB  =  BA               A + B = B + A         commutative
     (AB)C = A(BC)           (A+B)+C = A+(B+C)     associative

     A(B + C) = AB + AC      A + BC = (A + B)(A + C)    distributive
     ____   _   _             _______   _  _
     (AB) = A + B             (A + B) = A  B       de Morgan

NOT (A AND B) = (NOT A) OR (NOT B)    1st deMorgan in Pascal notation
           _         _
A XOR B = (A B) + (A B)               XOR Defined in terms of other operations
The deMorgan laws are of particular importance to programmers, they show an important relationship between AND, OR and NOT. When NOT is distributed over an expression, AND exchanges with OR. This is worth considering when constructing WHILE loops. Here is the proof of one by truth table:
A
B
A B
_____
(A B)
_
A
_
B
_   _
A + B
0
0
0
1
1
1
1
0
1
0
1
1
0
1
1
0
0
1
0
1
1
1
1
1
0
0
0
0
Compilers will store boolean values in a byte or word holding either a 0 or 1, this wastes some space. An array of boolean may be packed 32 values to a word, and shift instructions used to extract specific values. Usually any non-zero value will evaluate to TRUE because beqz, bnez instructions will be used.

The important point about logical instructions in assembly language is that they operate on all corresponding bits of the operands in parallel. There are no carries or other interaction between different bit positions.

MIPS logical instructions are all the 3 operand format, just like add and sub. Like them, the second source can be a constant. (Well, of course not only has destination and source registers. It is actually implemented by nor (not or) that follows the usual pattern.)

Masking

A common use of logical operations is isolating, or "masking" parts of words. Just like a mask covers your face and lets your eyes show through, a bit mask lets some bits show through, and hides others. Think of one operand as the "data" and the other as the "mask". This is a consequence of the identity and null laws above. For the different operations:

Example: Find the Source Register in an instruction

0x214afff3 addi $10, $10, -13
Instruction in binary:  00100001010010101111111111110011
Mask:                   00000011111000000000000000000000
Result of AND:          00000001010000000000000000000000
Shift right 21 bits     00000000000000000000000000001010
And we have the soruce register number, suitable for printing with syscall service 1.

Example: Changing case of letters

In order to make case-insensitive comparisons, both items being compared need to be in the same case. Only the letters should be converted, of course. By looking at the ASCII code chart, we see the only difference between the upper case and lower case letters is bit 5. To make a letter:

Example: Binary digit to ASCII character

Suppose we believe that register $t0 contains a binary number that can be represented by a single digit, 0..9. (This would be true for the remainder of division by 10.) From the ASCII table, we see that the numeric characters '0' .. '9' are the hex values 0x30 .. 0x39. All we have to do is OR the register with 0x30, and we have the required character, suitable for storing in a string.
                or  $t0, 0x30      #binary number to ASCII character

Example: Binary 4 bits to ASCII character for Hexadecimal - '0' .. '9' 'A' .. 'F'

If we want to represent 4 bits as a hex digit in a string, this isn't quite good enough, because the ASCII code 'A" doesnt follow immediately after '9', instead, there is a gap of 7 other characters. The solution is to test and add 7 if necessary. It is also a good idea to make sure only the lowest 4 bits are non-zero. In this coding example, we show what happens to the value 13, in binary 1101, in $t0:
     and   $t0, 0x000f      #00001101  only allow lowest 4 bits non-zero
     or    $t0, 0x30        #00111101
     ble   $t0,'9', numeric #00000111   to be added, since > '9'
        add  $t0, 7         #01000100   ASCII 'D'
numeric:

Example: Convert word to Hex string, 8 digits

This example uses the single digit conversion code above, and fills a string of 8 characters with the correct hex digits. To do this we need to shift the word being converted 4 bits right each time, apply the "binary to hex" conversion, and store the resulting character in the string right to left.
# function to convert word value to a hex string. Returns pointer to the string
#    arguments:
#        input:  a0, word to be converted
#        output: v0, pointer to the string
#    registers used:
#        t2 - working copy of word
#        t1 - point to string characters, right to left
#        t0 - single 4-bit value, converting to character, as above
word2hex:
    la     $v0, str8            #string workspace defined in data segment
    add    $t1, $v0, 8          #point to last character position + 1
    move   $t2, $a0             #copy input, will be shifting $t2
hexloop:    
      sub    $t1, 1             #point 1 character left
      and    $t0, $t2, 0x000f   #only allow lowest 4 bits non-zero, converting in $t0
      srl    $t2,$t2, 4         #shift copy of word so next 4 bits are on right
      or    $t0, 0x30           #make it numeric ASCII character, but ...
      ble    $t0,'9', numeric   #IF > '9', 
        add  $t0, 7             #must add 7 to get 'A' .. 'F'
numeric:
      sb     $t0, ($t1)         #store hex character in string
      bne    $t1,$v0, hexloop   #UNTIL all 8 characters stored
    jr     $ra                  #RETURN from function call, $v0 points to string
A fully running version using this code is hex1.a
Another way to do this, using rol, is given in the book, program hex.a.


Prepared by Lin Jensen, Bishop's University, 11 February 1999

Previous page Back   Next Next page       Contents