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
sourceimpl<const N: usize> Bits<N>
impl<const N: usize> Bits<N>
sourcepub fn any(&self) -> bool
pub fn any(&self) -> bool
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);
sourcepub fn all(&self) -> bool
pub fn all(&self) -> bool
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);
sourcepub fn xor(&self) -> bool
pub fn xor(&self) -> bool
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);
sourcepub fn index(&self) -> usize
pub fn index(&self) -> usize
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);
sourcepub fn len(&self) -> usize
pub fn len(&self) -> 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);
sourcepub fn count() -> u128
pub fn count() -> u128
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);
sourcepub fn get_bit(&self, index: usize) -> bool
pub fn get_bit(&self, index: usize) -> bool
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
sourcepub fn replace_bit(&self, index: usize, val: bool) -> Self
pub fn replace_bit(&self, index: usize, val: bool) -> Self
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));
sourcepub fn get_bits<const M: usize>(&self, index: usize) -> Bits<M>
pub fn get_bits<const M: usize>(&self, index: usize) -> Bits<M>
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))
sourcepub fn set_bits<const M: usize>(&mut self, index: usize, rhs: Bits<M>)
pub fn set_bits<const M: usize>(&mut self, index: usize, rhs: Bits<M>)
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));
sourcepub fn mask() -> Bits<N>
pub fn mask() -> Bits<N>
Returns a Bits value that contains [N] ones.
let x = Bits::<40>::mask();
assert_eq!(x, bits(0xFF_FFFF_FFFF));
sourcepub const fn width() -> usize
pub const fn width() -> usize
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);
Trait Implementations
sourceimpl<const N: usize> Add<Bits<N>> for LiteralType
impl<const N: usize> Add<Bits<N>> for LiteralType
sourceimpl<const N: usize> Binary for Bits<N>
impl<const N: usize> Binary for Bits<N>
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
sourceimpl<const N: usize> BitAnd<Bits<N>> for LiteralType
impl<const N: usize> BitAnd<Bits<N>> for LiteralType
sourceimpl<const N: usize> BitOr<Bits<N>> for LiteralType
impl<const N: usize> BitOr<Bits<N>> for LiteralType
sourceimpl<const N: usize> BitXor<Bits<N>> for LiteralType
impl<const N: usize> BitXor<Bits<N>> for LiteralType
sourceimpl<const N: usize> Default for Bits<N>
impl<const N: usize> Default for Bits<N>
Construct a default Bits - i.e., a zero bit vector of length N.
let x : Bits<200> = Default::default();
assert_eq!(x, bits(0));
sourceimpl<const N: usize> From<BigUint> for Bits<N>
impl<const N: usize> From<BigUint> for Bits<N>
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
sourceimpl<const N: usize> From<Bits<N>> for BigUint
impl<const N: usize> From<Bits<N>> for BigUint
sourceimpl<const N: usize> From<Bits<N>> for LiteralType
impl<const N: usize> From<Bits<N>> for LiteralType
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!
sourceimpl<const N: usize> From<Wrapping<u64>> for Bits<N>
impl<const N: usize> From<Wrapping<u64>> for Bits<N>
sourcefn from(x: Wrapping<LiteralType>) -> Self
fn from(x: Wrapping<LiteralType>) -> Self
Converts to this type from the input type.
sourceimpl<const N: usize> From<u64> for Bits<N>
impl<const N: usize> From<u64> for Bits<N>
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!
sourcefn from(x: LiteralType) -> Self
fn from(x: LiteralType) -> Self
Converts to this type from the input type.
sourceimpl Into<Bits<LogicState>> for I2CControllerCmd
impl Into<Bits<LogicState>> for I2CControllerCmd
sourceimpl Into<Bits<LogicState>> for I2CDriverCmd
impl Into<Bits<LogicState>> for I2CDriverCmd
sourceimpl Into<Bits<LogicState>> for SDRAMCommand
impl Into<Bits<LogicState>> for SDRAMCommand
sourceimpl<const N: usize> LowerHex for Bits<N>
impl<const N: usize> LowerHex for Bits<N>
Allows you to format a Bits as a lowercase hex string
let y = Bits::<16>::from(0xcafe);
println!("y = {:x}", y); // Prints y = cafe
sourceimpl Mul<Bits<16>> for Bits<16>
impl Mul<Bits<16>> for Bits<16>
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.
sourceimpl<const N: usize> Not for Bits<N>
impl<const N: usize> Not for Bits<N>
sourceimpl<const N: usize> Shl<Bits<N>> for LiteralType
impl<const N: usize> Shl<Bits<N>> for LiteralType
sourceimpl<const N: usize> Shr<Bits<N>> for LiteralType
impl<const N: usize> Shr<Bits<N>> for LiteralType
sourceimpl<const N: usize> Sub<Bits<N>> for LiteralType
impl<const N: usize> Sub<Bits<N>> for LiteralType
sourceimpl<const N: usize> UpperHex for Bits<N>
impl<const N: usize> UpperHex for Bits<N>
Allows you to format a Bits as an uppercase hex string
let y = Bits::<16>::from(0xcafe);
println!("y = {:X}", y); // Prints y = CAFE
impl<const N: usize> Copy for Bits<N>
Auto Trait Implementations
impl<const N: usize> RefUnwindSafe for Bits<N>
impl<const N: usize> Send for Bits<N>
impl<const N: usize> Sync for Bits<N>
impl<const N: usize> Unpin for Bits<N>
impl<const N: usize> UnwindSafe for Bits<N>
Blanket Implementations
sourceimpl<T> BorrowMut<T> for T where
T: ?Sized,
impl<T> BorrowMut<T> for T where
T: ?Sized,
const: unstable · sourcefn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
sourceimpl<Q, K> Equivalent<K> for Q where
Q: Eq + ?Sized,
K: Borrow<Q> + ?Sized,
impl<Q, K> Equivalent<K> for Q where
Q: Eq + ?Sized,
K: Borrow<Q> + ?Sized,
sourcefn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
Compare self to key
and return true
if they are equal.