pub enum Bits<const N: usize> {
    // some variants omitted
}
Expand description

The Bits type is used to capture values with arbitrarily large (but known) bit length

One significant difference between hardware design and software programming is the need (and indeed ability) to easily manipulate collections of bits that are of various lengths. While Rust has built in types to represent 8, 16, 32, 64, and 128 bits (at the time of this writing, anyway), it is difficult to represent a 5 bit type. Or a 256 bit type. Or indeed any bit length that differs from one of the supported values.

In hardware design, the bit size is nearly always unusual, as bits occupy physical space, and as a result, as a logic designer, you will intentionally use the smallest number of bits needed to capture a value. For example, if you are reading a single nibble at a time from a bus, this is clearly a 4 bit value, and storing it in a u8 is a waste of space and resources.

To model this behavior in RustHDL, we have the Bits type, which attempts to be as close as possible to a hardware bit vector. The size must be known at compile time, and there is some internal optimization for short bitvectors being represented efficiently, but ideally you should be able to think of it as a bit of arbitrary length. Note that the Bits type is Copy, which is quite important. This means in your RustHDL code, you can freely copy and assign bitvectors without worrying about the borrow checker or trying to call clone in the midst of your HDL.

For the most part, the Bits type is meant to act like a u32 or u128 type as far as your code is concerned. But the emulation of built-in types is not perfect, and you may struggle with them a bit.

Operations

Only a subset of operations are defined for Bits. These are the operations that can be synthesized in hardware without surprises (generally speaking). In Rust, you can operate between Bits types and other Bits of the same width, or you can use integer literals. Be careful! Especially when manipulating signed quantities.

Addition

You can perform wrapping addition using the + operator. Here are some simple examples of addition. First the version using a literal

let x: Bits<200> = bits(0xDEAD_BEEE);
let y: Bits<200> = x + 1;
assert_eq!(y, bits(0xDEAD_BEEF));

And now a second example that uses two Bits values

let x: Bits<40> = bits(0xDEAD_0000);
let y: Bits<40> = bits(0x0000_CAFE);
let z = x + y;
assert_eq!(z, bits(0xDEAD_CAFE));

Note that the addition operator is silently wrapping. In other words the carry bit is discarded silently (again - this is what hardware typically does). So you may find this result surprising:

let x: Bits<40> = bits(0xFF_FFFF_FFFF);
let y = x + 1;
assert_eq!(y, bits(0));

In this case, the addition of 1 caused [x] to wrap to all zeros. This is totally normal, and what one would expect from hardware addition (without a carry). If you need the carry bit, then the solution is to first cast to 1 higher bit, and then add, or alternately, to compute the carry directly.

let x: Bits<40> = bits(0xFF_FFFF_FFFF);
let y = bit_cast::<41, 40>(x) + 1;
assert_eq!(y, bits(0x100_0000_0000));

The order of the arguments does not matter. The bit width of the calculation will be determined by the Bits width.

let x : Bits<25> = bits(0xCAFD);
let y = 1 + x;
assert_eq!(y, bits(0xCAFE));

However, you cannot combine two different width Bits values in a single expression.

let x: Bits<20> = bits(0x1234);
let y: Bits<21> = bits(0x5123);
let z = x + y; // Won't compile!

Subtraction

Hardware subtraction is defined using 2-s complement representation for negative numbers. This is pretty much a universal standard for representing negative numbers in binary, and has the added advantage that a hardware subtractor can be built from an adder and some basic gates. Subtraction operates much like the Wrapping class. Note that overflow and underflow are not detected in RustHDL (nor are they detected in most hardware implementations either).

Here is a simple example with a literal and subtraction that does not cause udnerflow

let x: Bits<40> = bits(0xDEAD_BEF0);
let y = x - 1;
assert_eq!(y, bits(0xDEAD_BEEF));

When values underflow, the representation is still valid as a 2-s complement number. For example,

let x: Bits<16> = bits(0x40);
let y: Bits<16> = bits(0x60);
let z = x - y;
assert_eq!(z, bits(0xFFFF-0x20+1));

Here, we compare the value of z with 0xFFFF-0x20+1 which is the 2-s complement representation of -0x20.

You can also put the literal on the left side of the subtraction expression, as expected. The bitwidth of the computation will be driven by the width of the Bits in the expression.

let x = bits::<32>(0xBABE);
let z = 0xB_BABE - x;
assert_eq!(z, bits(0xB_0000));

Bitwise And

You can combine Bits using the and operator &. In general, avoid using the shortcut logical operator &&, since this operator is really only defined for logical (scalar) values of type bool.

let x: Bits<32> = bits(0xDEAD_BEEF);
let y: Bits<32> = bits(0xFFFF_0000);
let z = x & y;
assert_eq!(z, bits(0xDEAD_0000));

Of course, you can also use a literal value in the and operation.

let x: Bits<32> = bits(0xDEAD_BEEF);
let z = x & 0x0000_FFFF;
assert_eq!(z, bits(0xBEEF))

and similarly, the literal can appear on the left of the and expression.

let x: Bits<32> = bits(0xCAFE_BEEF);
let z = 0xFFFF_0000 & x;
assert_eq!(z, bits(0xCAFE_0000));

Just like all other binary operations, you cannot mix widths (unless one of the values is a literal).

let x: Bits<16> = bits(0xFEED_FACE);
let y: Bits<17> = bits(0xABCE);
let z = x & y; // Won't compile!

Bitwise Or

There is also a bitwise-OR operation using the | symbol. Note that the logical OR (or shortcut OR) operator || is not supported for Bits, as it is only defined for scalar boolean values.

let x : Bits<32> = bits(0xBEEF_0000);
let y : Bits<32> = bits(0x0000_CAFE);
let z = x | y;
assert_eq!(z, bits(0xBEEF_CAFE));

You can also use literals

let x : Bits<32> = bits(0xBEEF_0000);
let z = x | 0x0000_CAFE;
assert_eq!(z, bits(0xBEEF_CAFE));

The caveat about mixing Bits of different widths still applies.

Bitwise Xor

There is a bitwise-Xor operation using the ^ operator. This will compute the bitwise exclusive OR of the two values.

let x : Bits<32> = bits(0xCAFE_BABE);
let y : Bits<32> = bits(0xFF00_00FF);
let z = y ^ x;
let w = z ^ y; // XOR applied twice is a null-op
assert_eq!(w, x);

Bitwise comparison

The equality operator == can compare two Bits for bit-wise equality.

 let x: Bits<16> = bits(0x5ea1);
 let y: Bits<16> = bits(0xbadb);
 assert_eq!(x == y, false)

Again, it is a compile time failure to attempt to compare Bits of different widths.

 let x: Bits<15> = bits(52);
 let y: Bits<16> = bits(32);
 let z = x == y; // Won't compile - bit widths must match

You can compare to literals, as they will automatically extended (or truncated) to match the bitwidth of the Bits value.

let x : Bits<16> = bits(32);
let z = x == 32;
let y = 32 == x;
assert!(z);
assert!(y);

Unsigned comparison

The Bits type only supports unsigned comparisons. If you compare a Bits value to a signed integer, it will first convert the signed integer into 2s complement representation and then perform an unsigned comparison. That is most likely not what you want. However, until there is full support for signed integer computations, that is the behavior you get. Hardware signed comparisons require more circuitry and logic than unsigned comparisons, so the rationale is to not inadvertently bloat your hardware designs with sign-aware circuitry when you don’t explicitly invoke it. If you want signed values, use [Signed].

Here are some simple examples.

let x: Bits<16> = bits(52);
let y: Bits<16> = bits(13);
assert!(y < x)

We can also compare with literals, which RustHDL will expand out to match the bit width of the Bits being compared to.

let x: Bits<16> = bits(52);
let y = x < 135;  // Converts the 135 to a Bits<16> and then compares
assert!(y)

Shift Left

RustHDl supports left shift bit operations using the << operator. Bits that shift off the left end of the bit vector (most significant bits).

let x: Bits<16> = bits(0xDEAD);
let y = x << 8;
assert_eq!(y, bits(0xAD00));

Shift Right

Right shifting is also supported using the >> operator. Bits that shift off the right end of the the bit vector (least significant bits).

let x: Bits<16> = bits(0xDEAD);
let y = x >> 8;
assert_eq!(y, bits(0x00DE));

Implementations

The [any] function returns true if any of the individual bits are true, and false otherwise. This reduction operation is equivalent to a logical OR of all the bits in the vector.

let x : Bits<14> = bits(0xDEA);
assert_eq!(x.any(), true);
let y : Bits<14> = Bits::default();
assert_eq!(y.any(), false);

The [all] function returns true if all of the individual bits are true, and false otherwise. This reduction operation is equivalent to a logical AND of all the bits in the vector.

let x : Bits<14> = bits(0xDEA);
assert_eq!(x.all(), false);
let y : Bits<14> = bits(0x3FFF);
assert_eq!(y.all(), true);

The [xor] function computes the exclusive OR of all the bits in the vector. This is equivalent to counting the number of ones. If the number is odd, the XOR will be true. If even, it will be false.

let x: Bits<12> = bits(0b1100_0100_1100);
assert_eq!(x.xor(), true);
let y: Bits<12> = bits(0b1100_0110_1100);
assert_eq!(y.xor(), false);

The [index] function is used when a Bits is going to be used to index into an array or some other bit vector. This is typically a very specialized hardware operation, so there are limited cases in which it can be used. Also, there is an assumption that the Bits being used as an index is sufficiently small to fit in a natural word (assume 32 bits, here for safety). In practice, that means, that if you are indexing into a register using some other register/value, the length of the register is limited to a few billion bits.

let x: Bits<12> = bits(0b1100_0100_1100);
assert_eq!(x.index(), 0b1100_0100_1100_usize);

Return the number of bits in the current Bits. Because this is determined at compile time, it is of limited use as a runtime function, but is there nonetheless.

let x : Bits<14> = Bits::default();
assert_eq!(x.len(), 14);

Compute the number of possible values that a Bits can take. This is basically 2 raised to the Nth power. Because the result is returned as a usize, you must be careful, since this can easily overflow. A Bits<256> for example, cannot represent [count] on a normal 64 bit machine.

assert_eq!(Bits::<16>::count(), 1 << 16);

Extract the [index] bit from the given Bits. This will cause a runtime panic if the [index] bit is out of range of the width of the bitvector.

let x : Bits<14> = bits(0b10110);
assert_eq!(x.get_bit(0), false);
assert_eq!(x.get_bit(1), true);
assert_eq!(x.get_bit(2), true); // You get the idea

Replace the given bit of a Bits with a new bit value. This method leaves the original value alone, and returns a new Bits with all bits except the designated one left alone.

let x: Bits<16> = bits(0b1100_0000);
let x = x.replace_bit(0, true);
let x = x.replace_bit(7, false);
assert_eq!(x, bits(0b0100_0001));

Return a subset of bits from a Bits value, with a given offset. To preserve the feasibility of representing this in hardware, the width of the result must be fixed (the argument [M]), and only the offset can be computed.

let x: Bits<40> = bits(0xDEAD_BEEF_CA);
let y = x.get_bits::<32>(8);
assert_eq!(y, bits(0xDEAD_BEEF))

Set a group of bits in a value. This operation modifies the value in place.

let mut x: Bits<40> = bits(0xDEAD_BEEF_CA);
x.set_bits::<16>(8, bits(0xCAFE));
assert_eq!(x, bits(0xDEAD_CAFE_CA));

Returns a Bits value that contains [N] ones.

let x = Bits::<40>::mask();
assert_eq!(x, bits(0xFF_FFFF_FFFF));

Returns the width in bits of the [BitVec]. Note that this is the number of bits allocated. It does not depend on the value at all.

assert_eq!(Bits::<40>::width(), 40);

Convert Bits to an u8.

let x : Bits<6> = 12.into();
let y = x.to_u8();
assert_eq!(y, 12_u8);

Note that this will panic if the width of the bitvector is larger than 8 bits.

let x: Bits<12> = 0xADE.into();
let y = x.to_u8(); // Panics - too many bits

Convert Bits to an u16.

let x : Bits<12> = 12.into();
let y = x.to_u16();
assert_eq!(y, 12_u16);

Note that this will panic if the width of the bitvector is larger than 16 bits.

let x: Bits<20> = 0xADE.into();
let y = x.to_u16(); // Panics - too many bits

Convert Bits to an u32.

let x : Bits<24> = 12.into();
let y = x.to_u32();
assert_eq!(y, 12_u32);

Note that this will panic if the width of the bitvector is larger than 32 bits.

let x: Bits<40> = 0xADE.into();
let y = x.to_u32(); // Panics - too many bits

Convert Bits to an u64.

let x : Bits<40> = 12.into();
let y = x.to_u64();
assert_eq!(y, 12_u64);

Note that this will panic if the width of the bitvector is larger than 64 bits.

let x: Bits<80> = 0xADE.into();
let y = x.to_u64(); // Panics - too many bits

Convert Bits to an u128.

let x : Bits<80> = 12.into();
let y = x.to_u128();
assert_eq!(y, 12_u128);

Note that this will panic if the width of the bitvector is larger than 128 bits.

let x: Bits<140> = 0xADE.into();
let y = x.to_u128(); // Panics - too many bits

Trait Implementations

The resulting type after applying the + operator.

Performs the + operation. Read more

The resulting type after applying the + operator.

Performs the + operation. Read more

Allows you to format a Bits as a binary string

let y = Bits::<16>::from(0b1011_0100_0010_0000);
println!("y = {:b}", y); // Prints y = 1011010000100000

Formats the value using the given formatter.

The resulting type after applying the & operator.

Performs the & operation. Read more

The resulting type after applying the & operator.

Performs the & operation. Read more

The resulting type after applying the | operator.

Performs the | operation. Read more

The resulting type after applying the | operator.

Performs the | operation. Read more

The resulting type after applying the ^ operator.

Performs the ^ operation. Read more

The resulting type after applying the ^ operator.

Performs the ^ operation. Read more

Returns a copy of the value. Read more

Performs copy-assignment from source. Read more

Formats the value using the given formatter. Read more

Construct a default Bits - i.e., a zero bit vector of length N.

let x : Bits<200> = Default::default();
assert_eq!(x, bits(0));

Returns the “default value” for a type. Read more

Convert from a BigUint to a Bits. Will panic if the number of bits needed to represent the value are greater than the width of the Bits.

let x = BigUint::parse_bytes(b"10111000101", 2).unwrap();
let y : Bits<16> = x.into();
println!("y = {:x}", y); // Prints y = 02c5

The following will panic, because the value cannot be represented in the given number of bits.

let x = BigUint::parse_bytes(b"10111000101", 2).unwrap();
let y : Bits<12> = x.into(); // Panics

Converts to this type from the input type.

Convert from a Bits to a BigUint.

let x : Bits<128> = 0xDEAD_BEEF_CAFE_BABE_1234_5678_u128.to_bits();
let y : BigUint = x.into();
println!("y = {:x}", y); // Prints 0xDEAD_BEEF_CAFE_BABE_1234_5678

Converts to this type from the input type.

Convert a Bits back to u128. Until Rust supports larger integers, the u128 is the largest integer type you can use without resorting to BigUint. RustHDL will panic if you try to convert a Bits that is more than 128 bits to a literal. Even if the value in the bitvector would fit.

 let x: Bits<16> = 0xDEAD.into();
 let y: u128 = x.to_u128();
 assert_eq!(y, 0xDEAD);

The following will panic even through the literal value stored in the 256 bit vector is less than 128 bits.

let x : Bits<256> = 42.into();
let y: u128 = x.to_u128(); // Panics!

Converts to this type from the input type.

Converts to this type from the input type.

Convenience method that allows you to convert a boolean into a single bit-width Bits.

let x : Bits<1> = true.into();
assert_eq!(x, bits(1))

Convert from LiteralType to Bits. Because of some restrictions on how Rust’s type inference works, when you work with unsized literals (e.g., x = 3), there must either be a unique integer type that can fit the expression, or it will default to i32. Unfortunately, in general, Bits are used to represent unsigned types. The upshot of all this is that RustHDL selects one unsigned integer type to represent literals. Although not ideal, RustHDL uses a LiteralType (currently ‘u64’) to represent literals so as to make HDL expressions close to Verilog or VHDL. This choice should not affect any hardware implementation, as hardware registers need to be of Bits type.

let x: Bits<16> = 0xDEAD.into(); // This is interpreteed as a 128 bit constant by Rust

This example is the largest bit width literal you can express using current edition Rust:

let x: Bits<128> = 0xDEADBEEF_CAFEBABE_1234ABCD_00005EA1_u128.to_bits();

From a safety perspective, RustHDL will panic if the argument is too large to fit into the bit vector. Thus, this example will panic, since the literal cannot be fit into 16 bits without truncation:

let x: Bits<16> = 0xDEADBEEF.into(); // This will panic!

Converts to this type from the input type.

Converts this type into the (usually inferred) input type.

Converts this type into the (usually inferred) input type.

Converts this type into the (usually inferred) input type.

Converts this type into the (usually inferred) input type.

Convenience method for converting a 1-bit width Bits value into a boolean value.

let x : Bits<1> = bits(1);
let y : bool = x.into();
assert!(y)

Allows you to format a Bits as a lowercase hex string

let y = Bits::<16>::from(0xcafe);
println!("y = {:x}", y); // Prints y = cafe

Formats the value using the given formatter.

Multipliers are special, so we only implement multipliers that we think are synthesizable. In this case, we implement a 16 x 16 bit multiplier which yields a 32 bit result.

The resulting type after applying the * operator.

Performs the * operation. Read more

Bitwise inversion of a Bits vector The ! operator will invert each bit in a Bits vector.

let x : Bits<16> = bits(0xAAAA);
let y = !x;
assert_eq!(y, bits(0x5555))

The resulting type after applying the ! operator.

Performs the unary ! operation. Read more

The resulting type after applying the << operator.

Performs the << operation. Read more

The resulting type after applying the << operator.

Performs the << operation. Read more

The resulting type after applying the >> operator.

Performs the >> operation. Read more

The resulting type after applying the >> operator.

Performs the >> operation. Read more

The resulting type after applying the - operator.

Performs the - operation. Read more

The resulting type after applying the - operator.

Performs the - operation. Read more

Allows you to format a Bits as an uppercase hex string

let y = Bits::<16>::from(0xcafe);
println!("y = {:X}", y); // Prints y = CAFE

Formats the value using the given formatter.

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more

Immutably borrows from an owned value. Read more

Mutably borrows from an owned value. Read more

Compare self to key and return true if they are equal.

Returns the argument unchanged.

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

The alignment of pointer.

The type for initializers.

Initializes a with the given initializer. Read more

Dereferences the given pointer. Read more

Mutably dereferences the given pointer. Read more

Drops the object pointed to by the given pointer. Read more

The resulting type after obtaining ownership.

Creates owned data from borrowed data, usually by cloning. Read more

Uses borrowed data to replace owned data, usually by cloning. Read more

The type returned in the event of a conversion error.

Performs the conversion.

The type returned in the event of a conversion error.

Performs the conversion.