Skip to content
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

Open
DavidAntliff opened this issue May 21, 2024 · 3 comments
Open

How to implement selectable overlapping nested structs? #91

DavidAntliff opened this issue May 21, 2024 · 3 comments

Comments

@DavidAntliff
Copy link

DavidAntliff commented May 21, 2024

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):

Format A:

    Bit
      7: Format == 0
      6-4: Reserved
      3-0: Some 4-bit value of some significance: Value A

Format B:

    Bit
      7: Format == 1
      6-2: Some 5-bit value of some significance: Value B
      1-0: Reserved

        7          6            ..   3     2     1    0
--------------------------------------------------------
A:    | Format=0 | Reserved        | Value A           |
B:    | Format=1 | Value B                  | Reserved |

In bitfield-struct, this is possible by using structs for each overlapping region, and then putting them into an enum and defining const fn into_bits() and const 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, because bitfield-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.

@DavidAntliff
Copy link
Author

DavidAntliff commented May 21, 2024

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]`

@hecatia-elegua
Copy link
Owner

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.

@DavidAntliff
Copy link
Author

DavidAntliff commented Jul 8, 2024

@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!).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants