Bitwise operations

Many devices have registers. These are a small number of binary numbers stored in the device and used to control the device or read its sensors.

Often, several values are stored in one register. This means we have to do some programming to manipulate the separate values stored together in one register. For example a display may have an adjustable brightness and and adjustable contrast. These may be stored together in one register.

The problem arises when we have primitives to read or write an entire register, but we only want to read or write one of the values stored in the register.

For this we use a binary "mask". The mask indicates with '1' or '0' which bits we want to process. Mostly people use '1' to indicate the bits that should be processed, but either works. You just have to know which you are using.

Before we can explain the use of a mask we have to explain the bitwise boolean operations.

The "and" operation

The boolean bitwise "and" operation is an operation on two binary numbers that gives a result. It is written:

result = left & right

For each position in the binary number, the result is '1' only if both the inputs have '1' in that position. So:

  • 0 & 0 == 0
  • 0 & 1 == 0
  • 1 & 0 == 0
  • 1 & 1 == 1

For a single digit binary number the "&" operation is like multiplication, usually written with "*" on computers.

  • 0 * 0 == 0
  • 0 * 1 == 0
  • 1 * 0 == 0
  • 1 * 1 == 1

We can use the bitwise "and" operation with a mask value as follows. Let's say we want to change bits 3 to 7 in a value (bits are counted from the right, starting with 0). We can make a mask that has ones in positions 3 to 7. Binary numbers are written with 0b in Toit, and we can insert underscores anywhere we want to improve readability.

MASK := 0b000000000_1111_000

This mask value has zeros everywhere except in the area we are interested in, positions 3 to 7. For every mask there is the opposite mask, with all the bits reversed:

ANTI-MASK ::= 0b111111111_0000_111

If we have one mask, we can make the other one with the tilde (~) operator:

ANTI-MASK ::= ~MASK // This does the same.

Now if we want to change the bits 3-7 in a value to be 1010 regardless of what they were before we start by zeroing bits 3-7. This can be done with the anti-mask:

// This can also be written:  value &= ANTI-MASK
value = value & ANTI-MASK
value      101010101_0101_010
antimask   111111111_0000_111
           ------------------  & operation ("and")
new value  101010101_0000_010
                     ^^^^------- the zeroed bits are here.

The masking operation has zeroed the bits from 3-7, while the other bits in the value are untouched.

The "or" operation

To set the bits to a desired value, we use the "or" operation which is the pendant to the "and" operation, and is written with the vertical pipe sign "|".

  • 0 | 0 == 0
  • 0 | 1 == 1
  • 1 | 0 == 1
  • 1 | 1 == 1

Let's say we want to set bits 3-7 to 1100. We would use an "or" operation on a number with the bits in that place and zeroes in all other places.

value = value | 0b000000000_1100_000
new value  101010101_0000_010
number     000000000_1100_000
           ------------------  | operation ("or")
new2 value 101010101_1100_010
                     ^^^^------ the 1100 has been inserted here.

Putting it all together

Perhaps we have a device with an 8-bit register that has two fields. The low 4 bits control brightness, and the high 4 control contrast. There are some constants:

BRIGHTNESS-0 ::= 0b0000
BRIGHTNESS-1 ::= 0b0001
BRIGHTNESS-2 ::= 0b0010
BRIGHTNESS-3 ::= 0b0011
BRIGHTNESS-4 ::= 0b0100
BRIGHTNESS-5 ::= 0b0101
BRIGHTNESS-6 ::= 0b0110
BRIGHTNESS-7 ::= 0b0111
BRIGHTNESS-8 ::= 0b1000
BRIGHTNESS-9 ::= 0b1001
BRIGHTNESS-10 ::= 0b1010
BRIGHTNESS-11 ::= 0b1011
BRIGHTNESS-12 ::= 0b1100
BRIGHTNESS-13 ::= 0b1101
BRIGHTNESS-14 ::= 0b1110
BRIGHTNESS-15 ::= 0b1111

CONTRAST-0 ::=  0b0000_0000
CONTRAST-1 ::=  0b0001_0000
CONTRAST-2 ::=  0b0010_0000
CONTRAST-3 ::=  0b0011_0000
CONTRAST-4 ::=  0b0100_0000
CONTRAST-5 ::=  0b0101_0000
CONTRAST-6 ::=  0b0110_0000
CONTRAST-7 ::=  0b0111_0000
CONTRAST-8 ::=  0b1000_0000
CONTRAST-9 ::=  0b1001_0000
CONTRAST-10 ::= 0b1010_0000
CONTRAST-11 ::= 0b1011_0000
CONTRAST-12 ::= 0b1100_0000
CONTRAST-13 ::= 0b1101_0000
CONTRAST-14 ::= 0b1110_0000
CONTRAST-15 ::= 0b1111_0000

We only have the operations read_register and write_register, but we want to adjust the two values independently:

class MyDevice extends OneRegisterDevice:

  // Constructor...

  get-brightness -> int:
    reg := read-register
    return reg & BRIGHTNESS-MASK_

  get-contrast -> int:
    reg := read-register
    return reg & CONTRAST_MASK_

  set-brightness brightness/int -> none:
    reg := read-register
    // Remove the old brightness with the mask.
    reg &= ~BRIGHTNESS-MASK_
    // Insert the new brightness with the "or" operation.
    reg |= brightness
    // Write back the old contrast and the new brightness.
    write-register reg

  set-contrast contrast/int -> none:
    reg := read-register
    // Remove the old brightness with the mask.
    reg &= ~CONTRAST-MASK_
    // Insert the new contrast with the "or" operation.
    reg |= contrast
    // Write back the new contrast and the old brightness.
    write-register reg

  BRIGHTNESS-MASK_ ::= 0b0000_1111
  CONTRAST-MASK_   ::= 0b1111_0000

Using the shift operations

In the example above, the user of the driver could choose between 16 discrete contrast levels, but perhaps the user wants to do a calculation. In that case they have perhaps calculated number between 0 and 15, and would like to use that. We can do that with the help of the shift operators, "<<" and ">>".

The shift operations move the bits of a binary number around. For example, the ">>" operator moves them to the right.

value :=        0b00101000000
value = value >> 3
// Now value == 0b00000101000
value <<= 4
// Now value == 0b01010000000

Using this operation we could rewrite our MyDevice to take values from 0 to 15 instead of the constants:

class MyDevice extends OneRegisterDevice:
  // Constructors...

  /// Get the current brightness from 0-15.
  get-brightness -> int:
    reg := read-register
    return (reg & BRIGHTNESS-MASK_) >> BRIGHTNESS-SHIFT_

  /// Get the current contrast from 0-15.
  get-contrast -> int:
    reg := read-register
    return (reg & CONTRAST-MASK_) >> CONTRAST-SHIFT_

  /// Set the current brightness from 0-15.
  set-brightness brightness/int -> none:
    assert: 0 <= brightness <= 15
    reg := read-register
    // Remove the old brightness with the mask.
    reg &= ~BRIGHTNESS-MASK_
    // Insert the new brightness with the "or" operation.
    reg |= brightness << BRIGHTNESS-SHIFT_
    // Write back the old contrast and the new brightness.
    write-register reg

  /// Set the current contrast from 0-15.
  set-contrast contrast/int -> none:
    assert: 0 <= contrast <= 15
    reg := read-register
    // Remove the old contrast with the mask.
    reg &= ~CONTRAST-MASK_
    // Insert the new contrast with the "or" operation.
    reg |= contrast << CONTRAST-SHIFT_
    // Write back the new contrast and the old brightness.
    write-register reg

  // The brightness is stored in the low 4 bits, and the
  // contrast is stored in the high 4 bits.
  BRIGHTNESS-MASK_  ::= 0b0000_1111
  CONTRAST-MASK_    ::= 0b1111_0000
  BRIGHTNESS-SHIFT_ ::= 0
  CONTRAST-SHIFT_   ::= 4