svd2zig
svd2zig copied to clipboard
destination type 'u32' has size 4 but source type has size 5
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);
...
[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
Yeah, looks like a bug.
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);
}
Thanks for the workaround @rbino
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.