Open DavidAntliff opened 5 months ago
I should note that when I tried this with an enum, I ran into issues with the enum variants not being "unit"
#[bitsize(4)]
#[derive(DebugBits, FromBits)]
struct Inner1 {
register: u4,
}
#[bitsize(20)]
#[derive(DebugBits, FromBits)]
struct Inner2 {
address: u20,
}
#[bitsize(20)]
#[derive(Debug, FromBits)]
enum Inner {
Inner1(Inner1),
Inner2(Inner2),
}
#[bitsize(32)]
#[derive(DebugBits, FromBits)]
struct Outer {
inner: Inner,
reserved: u12,
}
let v = Outer::new(
Inner::Inner1(Inner1::new(u4::new(7)))
);
error: FromBits only supports unit variants for variants without `#[fallback]`
related: #80 #13
Sooo, it is either a union, which would make implementing this easier (probably), or it is really a tagged union (enum). For a tagged union, in memory, the tag can either be above the union, or somewhere else entirely. Your Format A, B example and many other hardware registers make it easy by just putting it above, or inside the same register.
That would mean we just need #80 when the discriminant is above, or something like this if it's close by:
struct Outer {
inner: Inner,
#[discriminant_of(inner)]
inner_is_1: bool,
reserved: u11,
}
(don't ask me how it would be mapped to hardware, I don't think there is a #[repr]
for Inner
which would keep the size correct)
But then we also have another problem, because some hardware, IIRC, has this discriminant laying around on the other side of the world. Might be one of those things where we should implement the solution to the simplest problems first.
@hecatia-elegua sorry I can't really comment in the implementation at this point. But for use-case analysis, perhaps it would help if I share how I used this in bitfield-struct
. Here's are two "reusable" fields (don't worry about the specific domain I'm working in, it would take too long to explain it):
A 4-bit "register" field:
#[bitfield(u8)]
struct RegisterField {
#[bits(4)]
register: u8,
#[bits(4)]
__: u8,
}
A 20-bit "duration" field that starts at the same bit offset (the idea is that the top-level register uses one or the other, never both at the same time):
#[bitfield(u32)]
struct DurationField {
#[bits(20)]
duration: u32,
#[bits(12)]
__: u16,
}
And now I have, based on a bit elsewhere in the top-level "register", the choice between these two:
enum RegisterOrDurationField {
Register(RegisterField),
Duration(DurationField),
}
impl RegisterOrDurationField {
const fn into_bits(self) -> u32 {
match self {
Self::Register(inner) => inner.into_bits() as u32,
Self::Duration(inner) => inner.into_bits(),
}
}
const fn from_bits(bits: u32) -> Self {
// Not enough information available to determine variant, so default to Duration
Self::Duration(DurationField::from_bits(bits))
}
fn set(value: u32, is_register: bool) -> Result<Self, SomeEncodeError> {
Ok(match is_register {
true => {
RegisterOrDurationField::Register(RegisterField::new().with_register(value as u8)),
}
false => {
RegisterOrDurationField::Duration(
DurationField::new().with_duration(value as u32),
)
}
})
}
}
Then I might make use of this in a high-level "register" like this:
#[bitfield(u128)]
pub(crate) struct EncodedInstruction {
#[bits(20)]
register_or_duration: RegisterOrDurationField,
#[bits(1)]
arg_type: bool, // register or duration?
#[bits(5)]
__: u32,
#[bits(6, access=RO, default=OPCODE)]
opcode: u8,
#[bits(24)]
__: u32,
#[bits(6)]
extra: u8,
#[bits(66)]
__: u128,
}
impl EncodedInstruction {
pub fn encode(
value: u32,
is_register: bool,
) -> Result<Self, SomeEncodeError> {
let mut instruction = Self::new().with_extra(42);
let dur_or_reg = RegisterOrDurationField::set(value, true)?;
instruction.set_register_or_duration(dur_or_reg);
instruction.set_arg_type(is_register);
Ok(instruction)
}
}
Using this technique I am able to build up top-level registers from reusable "fields".
I hope this makes sense? If I could do something very similar in bilge then it would be a good alternative crate to bitfield-struct for me. I particularly like the u20
, u4
, etc types in bilge, rather than the excessively large backing types in bitfield-struct, although they do combine as I'd expect (i.e. using a u32 for a 20-bit register doesn't splat the upper 12 bits when combined in this manner, thankfully!).
I've read the "nested struct" example: https://github.com/hecatia-elegua/bilge/blob/main/examples/nested_structs.rs. This is useful when fields are reused between different structs.
There's another use-case that is common in embedded systems - that where such nested structs can be optionally used, usually depending on the value of some other field, but exist in the same place in the register. I.e. they overlap. Essentially, the register has multiple interpretations.
For a simple example, imagine an 8-bit register, where the top-most bit ("Format") indicates:
0b_0xxx_xxxx: it's a register of Format A, 0b_1xxx_xxxx: it's a register of Format B,
And these formats could be something like (apologies for bad ASCII art):
In
bitfield-struct
, this is possible by using structs for each overlapping region, and then putting them into anenum
and definingconst fn into_bits()
andconst fn from_bits()
for the enum type.I wasn't able to find a way to do something similar with
bilge
- is there a way?The limitation in
bitfield-struct
is that the overlapping structs need to be the same width. However, becausebitfield-struct
only supports structs that are 8, 16, ... 128 bits wide, this is quite awkward for field overlaps that might be, say, 13 bits wide. This leads to problems composing the top-level register layout.Note: of course for this simple example, in
bilge
I could just create two separate top-level structs, one for Format A, one for Format B, but in real life, these selectable formats often occur multiple times within a single register. For example, in my case I Have 128-bit registers and there are multiple sections that can be swapped out for other formats by the setting of just a few format bits. This makes the number of possible formats combinatorially large.One real-life example is a 51-bit field (in a 128-bit register) that represents a "source" for some information - the top 3 bits represent how to interpret the remaining 32 bits, that could be one of several things: IPv4 address (bottom 32 bits), MAC address (48 bits), index to some table somewhere (bottom 12 bits), or a status code indicating an error fetching the information (bottom 8 bits).
bilge
0.2.0.