-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to implement selectable overlapping nested structs? #91
Comments
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)))
);
|
Sooo, it is either a union, which would make implementing this easier (probably), or it is really a tagged union (enum). 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 But then we also have another problem, because some hardware, IIRC, has this discriminant laying around on the other side of the world. |
@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 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 |
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.The text was updated successfully, but these errors were encountered: