lynaghk / svd2zig

Generate Zig API from SVD register definitions.
MIT License
36 stars 2 forks source link

destination type 'u32' has size 4 but source type has size 5 #5

Open lynaghk opened 3 years ago

lynaghk commented 3 years ago

When trying to write to a register, I'm getting a size mismatch.

HW.tim3.cr1.write(.{ .cen = .enabled });

gives the error:

./src/hw/stm32f0x0.zig:29:39: error: destination type 'u32' has size 4 but source type 'hw.stm32f0x0.cr1_val' has size 5
            self.raw_ptr.* = @bitCast(u32, value);
@compileLog(@bitSizeOf(HW.tim3.cr1_val));
@compileLog(@sizeOf(HW.tim3.cr1_val));

gives 32 and 5.

So I guess it's an alignment issue? Not sure how to fix.

Here's the definition, just FYI:

///General-purpose-timers
pub const tim3 = struct {

    //////////////////////////
    ///CR1
    pub const cr1_val = packed struct {
        ///CEN [0:0]
        ///Counter enable
        cen: packed enum(u1) {
            ///Counter disabled
            disabled = 0,
            ///Counter enabled
            enabled = 1,
        } = .disabled,
        ///UDIS [1:1]
        ///Update disable
        udis: packed enum(u1) {
            ///Update event enabled
            enabled = 0,
            ///Update event disabled
            disabled = 1,
        } = .enabled,
        ///URS [2:2]
        ///Update request source
        urs: packed enum(u1) {
            ///Any of counter overflow/underflow, setting UG, or update through slave mode, generates an update interrupt or DMA request
            any_event = 0,
            ///Only counter overflow/underflow generates an update interrupt or DMA request
            counter_only = 1,
        } = .any_event,
        ///OPM [3:3]
        ///One-pulse mode
        opm: packed enum(u1) {
            ///Counter is not stopped at update event
            disabled = 0,
            ///Counter stops counting at the next update event (clearing the CEN bit)
            enabled = 1,
        } = .disabled,
        ///DIR [4:4]
        ///Direction
        dir: packed enum(u1) {
            ///Counter used as upcounter
            up = 0,
            ///Counter used as downcounter
            down = 1,
        } = .up,
        ///CMS [5:6]
        ///Center-aligned mode
        ///selection
        cms: packed enum(u2) {
            ///The counter counts up or down depending on the direction bit
            edge_aligned = 0,
            ///The counter counts up and down alternatively. Output compare interrupt flags are set only when the counter is counting down.
            center_aligned1 = 1,
            ///The counter counts up and down alternatively. Output compare interrupt flags are set only when the counter is counting up.
            center_aligned2 = 2,
            ///The counter counts up and down alternatively. Output compare interrupt flags are set both when the counter is counting up or down.
            center_aligned3 = 3,
        } = .edge_aligned,
        ///ARPE [7:7]
        ///Auto-reload preload enable
        arpe: packed enum(u1) {
            ///TIMx_APRR register is not buffered
            disabled = 0,
            ///TIMx_APRR register is buffered
            enabled = 1,
        } = .disabled,
        ///CKD [8:9]
        ///Clock division
        ckd: packed enum(u2) {
            ///t_DTS = t_CK_INT
            div1 = 0,
            ///t_DTS = 2 × t_CK_INT
            div2 = 1,
            ///t_DTS = 4 × t_CK_INT
            div4 = 2,
        } = .div1,
        _unused10: u22 = 0,
    };
    ///control register 1
    pub const cr1 = Register(cr1_val).init(0x40000400 + 0x0);
...
jamii commented 3 years ago
[nix-shell:~]$ cat test.zig
pub const s1 = packed struct {
    a: u8,
    b: u16,
};

pub const s2 = packed struct {
    a: u8,
    b: u17,
};

pub fn main() void {
    @compileLog(@sizeOf(s1));
    @compileLog(@sizeOf(s2));
}

[nix-shell:~]$ tmp/zig-linux-x86_64-0.8.0-dev.1860+1fada3746/zig run ./test.zig
| 3
| 5
jamii commented 3 years ago

Yeah, looks like a bug.

rbino commented 3 years ago

While debugging the same issue in svd4zig I ended up doing this.This seems to derive from https://github.com/ziglang/zig/issues/2627.

Empirically, it seems the problem arises when portions of registers "cross" 16-bit boundaries. Just to be on the safe side, my code splits unused bits on 8-bit boundaries (I only do this on unused portions since I would like to keep everything else as-is). This is not always the case though (see last test below, which crosses the boundary but gets the correct size).

Here are some tests based on the example above that try to move around some stuff to see what happens:

const assert = @import("std").debug.assert;

pub const fail_orig = packed struct {
    cen: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    udis: packed enum(u1) {
        enabled = 0,
        disabled = 1,
    } = .enabled,
    urs: packed enum(u1) {
        any_event = 0,
        counter_only = 1,
    } = .any_event,
    opm: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    dir: packed enum(u1) {
        up = 0,
        down = 1,
    } = .up,
    cms: packed enum(u2) {
        edge_aligned = 0,
        center_aligned1 = 1,
        center_aligned2 = 2,
        center_aligned3 = 3,
    } = .edge_aligned,
    arpe: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    ckd: packed enum(u2) {
        div1 = 0,
        div2 = 1,
        div4 = 2,
    } = .div1,
    // Initial failing example, this unused piece crosses the 16-bit boundary
    // The struct has size 5
    _unused10: u22 = 0,
};

pub const ok = packed struct {
    cen: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    udis: packed enum(u1) {
        enabled = 0,
        disabled = 1,
    } = .enabled,
    urs: packed enum(u1) {
        any_event = 0,
        counter_only = 1,
    } = .any_event,
    opm: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    dir: packed enum(u1) {
        up = 0,
        down = 1,
    } = .up,
    cms: packed enum(u2) {
        edge_aligned = 0,
        center_aligned1 = 1,
        center_aligned2 = 2,
        center_aligned3 = 3,
    } = .edge_aligned,
    arpe: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    ckd: packed enum(u2) {
        div1 = 0,
        div2 = 1,
        div4 = 2,
    } = .div1,
    // Splitting this so that it doesn't go over 16-bit boundary
    // The struct has size 4
    _unused10: u6 = 0,
    _unused16: u16 = 0,
};

pub const fail_invert = packed struct {
    cen: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    udis: packed enum(u1) {
        enabled = 0,
        disabled = 1,
    } = .enabled,
    urs: packed enum(u1) {
        any_event = 0,
        counter_only = 1,
    } = .any_event,
    opm: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    dir: packed enum(u1) {
        up = 0,
        down = 1,
    } = .up,
    cms: packed enum(u2) {
        edge_aligned = 0,
        center_aligned1 = 1,
        center_aligned2 = 2,
        center_aligned3 = 3,
    } = .edge_aligned,
    arpe: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    ckd: packed enum(u2) {
        div1 = 0,
        div2 = 1,
        div4 = 2,
    } = .div1,
    // If we invert them, they cross the 16-bit boundary again
    // The struct has size 5
    _unused16: u16 = 0,
    _unused10: u6 = 0,
};

pub const fail_2bit_cross = packed struct {
    cen: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    udis: packed enum(u1) {
        enabled = 0,
        disabled = 1,
    } = .enabled,
    urs: packed enum(u1) {
        any_event = 0,
        counter_only = 1,
    } = .any_event,
    opm: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    dir: packed enum(u1) {
        up = 0,
        down = 1,
    } = .up,
    cms: packed enum(u2) {
        edge_aligned = 0,
        center_aligned1 = 1,
        center_aligned2 = 2,
        center_aligned3 = 3,
    } = .edge_aligned,
    arpe: packed enum(u1) {
        disabled = 0,
        enabled = 1,
    } = .disabled,
    ckd: packed enum(u2) {
        div1 = 0,
        div2 = 1,
        div4 = 2,
    } = .div1,
    _unused10: u5 = 0,
    // Here we minimize the crossing part, only these 2 bits (15-16) are crossing
    // The struct has size 5
    _unused15: u2 = 0,
    _unused16: u15 = 0,
};

pub const fail_minimal_cross = packed struct {
    _stuff1: u8,
    // This crosses the 16-bit boundary
    // This struct has size 5 
    _stuff2: u24,
};

pub const ok_but_cross = packed struct {
    _stuff1: u7,
    // This crosses the 16-bit boundary too
    // But the struct has size 4
    _stuff2: u25,
};

test "register size" {
  assert(@sizeOf(fail_orig) == 5);
  assert(@sizeOf(ok) == 4);
  assert(@sizeOf(fail_invert) == 5);
  assert(@sizeOf(fail_2bit_cross) == 5);
  assert(@sizeOf(fail_minimal_cross) == 5);
  assert(@sizeOf(ok_but_cross) == 4);
}
jamii commented 3 years ago

Thanks for the workaround @rbino

lynaghk commented 3 years ago

Yikes. Good research @rbino! Not sure how I want to resolve this --- either impl a workaround in my register generation or wait until it gets fixed upstream. Will leave this open until then.