danlehmann / bitfield

Rust crate for bitfields and bit-enums
MIT License
47 stars 9 forks source link

bitbybit: Bit fields and bit enums

This crate provides macros that create bit fields and bit enums, which are useful in bit packing code (e.g. in drivers or networking code).

Some highlights:

Basic declaration

A bit field is created similar to a regular Rust struct. Annotations define the layout of the structure. As an example, consider the following definition, which specifies a bit field:

#[bitfield(u32)]
struct GICD_TYPER {
    #[bits(11..=15, r)]
    lspi: u5,

    #[bit(10, r)]
    security_extn: bool,

    #[bits(5..=7, r)]
    cpu_number: u3,

    #[bits(0..=4, r)]
    itlines_number: u5,
}

How this works:

Enumerations

Very often, fields aren't just numbers but really enums. This is supported by first defining a bitenum and then using that inside of a bitfield:

#[bitenum(u2, exhaustive = false)]
enum NonExhaustiveEnum {
    Zero = 0b00,
    One = 0b01,
    Two = 0b10,
}

#[bitenum(u2, exhaustive = true)]
enum ExhaustiveEnum {
    Zero = 0b00,
    One = 0b01,
    Two = 0b10,
    Three = 0b11,
}

#[bitfield(u64, default = 0)]
struct BitfieldWithEnum {
    #[bits(2..=3, rw)]
    e2: Option<NonExhaustiveEnum>,

    #[bits(0..=1, rw)]
    e1: ExhaustiveEnum,
}

Arrays

Sometimes, bits inside bitfields are repeated. To support this, this crate allows specifying bitwise arrays. For example, the following struct gives read/write access to each individual nibble (hex character) of a u64:

#[bitfield(u64, default = 0)]
struct Nibble64 {
     #[bits(0..=3, rw)]
     nibble: [u4; 16],
}

Arrays can also have a stride. This is useful in the case of multiple smaller values repeating. For example, the following definition provides access to each bit of each nibble:

#[bitfield(u64, default = 0)]
struct NibbleBits64 {
    #[bit(0, rw, stride = 4)]
    nibble_bit0: [bool; 16],

    #[bit(1, rw, stride = 4)]
    nibble_bit1: [bool; 16],

    #[bit(2, rw, stride = 4)]
    nibble_bit2: [bool; 16],

    #[bit(3, rw, stride = 4)]
    nibble_bit3: [bool; 16],
}

Default

Bitfields can always be created through new_with_raw_value():

let a = NibbleBits64::new_with_raw_value(0x43218765);

However, pretty often a specific value can be considered the default (for example 0). This can be specified like this:

#[bitfield(u32, default = 0x1234)]
struct Bitfield1 {
  #[bits(0..=3, rw)]
  nibble: [u4; 4],
}

If a default value is specified, the bitfield can easily be created with this specific value:

let a = Bitfield1::Default;
const A: Bitfield1 = Bitfield1::DEFAULT;

Default values are used as-is, even if they affect bits that aren't defined within the bitfield.

Setting all fields at once using the builder syntax

It is possible to set all fields at once, like this:

const T: Test = Test::builder()
    .with_baudrate(0x12)
    .with_some_other_bits(u4::new(0x2))
    .with_array([1, 2, 3, 4])
    .build();

Using builder() it is impossible to forget setting any fields. This is checked at compile time: If any field is not set, build() can not be called.

At the moment, it is required to set all fields in the same order as they are specified. As Rust's const generics become more powerful, this restriction might be lifted.

For the builder() to be available, the following has to be true:

Non-contiguous bitranges

Occasionally it can be useful for bitranges to not be contiguous. For example, RISC-V defines some immediates in a way that they have to be reassembled. This can be achieved like this:

  #[bitfield(u32)]
  struct SBFormat {
      #[bits([8..=11, 25..=30, 7, 31], rw)]
      imm_half: u12,
  }

Dependencies

Arbitrary bit widths like u5 or u67 do not exist in Rust at the moment. Therefore, the following dependency is required:

arbitrary-int = "1.2.7"

Usage

Even though bitfields feel somewhat like structs, they are internally implemented as simple data types like u32. Therefore, they provide an immutable interface: Instead of changing the value of a field, any change operation will return a new bitfield with that field modified.

let a = NibbleBits64::new_with_raw_value(0x12345678_ABCDEFFF);
// Read a value
assert_eq!(u4::new(0xE), a.nibble(3));
// Change a value
let b = a.with_nibble(0, u4::new(0x3))
assert_eq!(0x12345678_ABCDEFF3, b.raw_value());