From 4ccf5ed4a9c6eb8e714818a696af001aaec7d2b1 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Wed, 8 Jul 2026 02:10:12 +0200 Subject: [PATCH 01/17] more ergonomic xml value casting --- tools/regz/src/atdf.zig | 83 +++++++++++-------------------------- tools/regz/src/targetdb.zig | 43 +++++++------------ tools/regz/src/xml.zig | 14 +++++-- 3 files changed, 50 insertions(+), 90 deletions(-) diff --git a/tools/regz/src/atdf.zig b/tools/regz/src/atdf.zig index d4b5649af..11a37f00f 100644 --- a/tools/regz/src/atdf.zig +++ b/tools/regz/src/atdf.zig @@ -131,8 +131,7 @@ fn load_interrupt_group(ctx: *Context, node: xml.Node, device_id: DeviceID) !voi const db = ctx.db; const module_instance = node.get_attribute("module-instance") orelse return error.MissingModuleInstance; const name_in_module = node.get_attribute("name-in-module") orelse return error.MissingNameInModule; - const index_str = node.get_attribute("index") orelse return error.MissingInterruptGroupIndex; - const index = try std.fmt.parseInt(i32, index_str, 0); + const index = try node.get_attribute_int(i32, "index") orelse return error.MissingInterruptGroupIndex; if (ctx.interrupt_groups.get(name_in_module)) |group_list| { for (group_list.items) |entry| { @@ -250,8 +249,7 @@ fn infer_enum_size(allocator: Allocator, module_node: xml.Node, value_group_node var max_value: u64 = 0; var value_it = value_group_node.iterate(&.{}, &.{"value"}); while (value_it.next()) |value_node| { - const value_str = value_node.get_attribute("value") orelse continue; - const value = try std.fmt.parseInt(u64, value_str, 0); + const value = try value_node.get_attribute_int(u64, "value") orelse continue; max_value = @max(value, max_value); } @@ -268,8 +266,7 @@ fn infer_enum_size(allocator: Allocator, module_node: xml.Node, value_group_node if (bitfield_node.get_attribute("values")) |values| { if (std.mem.eql(u8, values, value_group_name)) { log.debug("found values={s}", .{values}); - const mask_str = bitfield_node.get_attribute("mask") orelse continue; - const mask = try std.fmt.parseInt(u64, mask_str, 0); + const mask = try bitfield_node.get_attribute_int(u64, "mask") orelse continue; try field_sizes.append(allocator, @popCount(mask)); } } @@ -384,10 +381,7 @@ fn load_module_interrupt_group_entry( try list.append(ctx.db.gpa, .{ .name = node.get_attribute("name") orelse return error.MissingInterruptName, - .index = if (node.get_attribute("index")) |index_str| - try std.fmt.parseInt(i32, index_str, 0) - else - return error.MissingInterruptIndex, + .index = try node.get_attribute_int(i32, "index") orelse return error.MissingInterruptIndex, .description = node.get_attribute("caption"), }); } @@ -443,36 +437,26 @@ fn load_nested_register_group( try db.add_nested_struct_field(parent, .{ .name = name, .struct_id = struct_id, - .offset_bytes = if (node.get_attribute("offset")) |offset_str| - try std.fmt.parseInt(u64, offset_str, 0) - else - return error.MissingRegisterOffset, + .offset_bytes = try node.get_attribute_int(u64, "offset") orelse return error.MissingRegisterOffset, .description = node.get_attribute("caption"), - - .size_bytes = if (node.get_attribute("size")) |size_str| - try std.fmt.parseInt(u64, size_str, 0) - else - null, - .count = if (node.get_attribute("count")) |count_str| - try std.fmt.parseInt(u64, count_str, 0) - else - null, + .size_bytes = try node.get_attribute_int(u64, "size"), + .count = try node.get_attribute_int(u64, "count"), }); } fn infer_register_group_offset(ctx: *Context, node: xml.Node, struct_id: StructID) !void { // collect register offsets - var min: ?u64 = null; + var min: u64 = std.math.maxInt(u64); var register_it = node.iterate(&.{}, &.{ "register", "register-group" }); + while (register_it.next()) |register_node| { - const offset_str = register_node.get_attribute("offset") orelse continue; - const offset_bytes = std.fmt.parseInt(u64, offset_str, 0) catch continue; - min = @min(if (min) |m| m else offset_bytes, offset_bytes); + const offset_bytes = register_node.get_attribute_int(u64, "offset") catch continue orelse continue; + min = @min(min, offset_bytes); } - if (min) |m| { - try ctx.inferred_register_group_offsets.put(ctx.arena.allocator(), struct_id, m); - log.debug("inferred offset of {f}: {} bytes", .{ struct_id, m }); + if (min != std.math.maxInt(u64)) { + try ctx.inferred_register_group_offsets.put(ctx.arena.allocator(), struct_id, min); + log.debug("inferred offset of {f}: {} bytes", .{ struct_id, min }); } } @@ -493,10 +477,7 @@ fn load_register_group(ctx: *Context, node: xml.Node, parent: PeripheralID) !voi const struct_id = try db.create_nested_struct(parent_struct_id, .{ .name = node.get_attribute("name") orelse return error.NoName, .description = node.get_attribute("caption"), - .size_bytes = if (node.get_attribute("size")) |size_str| - try std.fmt.parseInt(u64, size_str, 0) - else - null, + .size_bytes = try node.get_attribute_int(u64, "size"), }); try infer_register_group_offset(ctx, node, struct_id); @@ -584,22 +565,14 @@ fn load_register( const register_id = try db.create_register(parent, .{ .name = name, .description = node.get_attribute("caption"), - .size_bits = if (node.get_attribute("size")) |size_str| - @as(u64, 8) * try std.fmt.parseInt(u64, size_str, 0) - else - return error.MissingRegisterSize, - .offset_bytes = if (node.get_attribute("offset")) |offset_str| - try std.fmt.parseInt(u64, offset_str, 0) - register_group_offset + .size_bits = (try node.get_attribute_int(u64, "size") orelse + return error.MissingRegisterSize) * 8, + .offset_bytes = if (try node.get_attribute_int(u64, "offset")) |offset| + offset - register_group_offset else return error.MissingRegisterOffset, - .count = if (node.get_attribute("count")) |count_str| - try std.fmt.parseInt(u64, count_str, 0) - else - null, - .reset_value = if (node.get_attribute("initval")) |initval_str| - try std.fmt.parseInt(u64, initval_str, 0) - else - null, + .count = try node.get_attribute_int(u64, "count"), + .reset_value = try node.get_attribute_int(u64, "initval"), .access = if (node.get_attribute("rw")) |access_str| try access_from_string(access_str) else @@ -894,8 +867,7 @@ fn load_module_instance_from_peripheral( const offset_bytes: u64 = if (get_inlined_register_group(node, name)) |register_group_node| blk: { log.debug("inlining {s}", .{name}); - const offset_str = register_group_node.get_attribute("offset") orelse return error.MissingPeripheralOffset; - const offset = try std.fmt.parseInt(u64, offset_str, 0); + const offset = try register_group_node.get_attribute_int(u64, "offset") orelse return error.MissingPeripheralOffset; break :blk offset; } else { return error.Todo; @@ -940,8 +912,7 @@ fn load_module_instance_from_register_group( const name = node.get_attribute("name") orelse return error.MissingInstanceName; const name_in_module = register_group_node.get_attribute("name-in-module") orelse return error.MissingNameInModule; - const offset_str = register_group_node.get_attribute("offset") orelse return error.MissingOffset; - const offset = try std.fmt.parseInt(u64, offset_str, 0); + const offset = try register_group_node.get_attribute_int(u64, "offset") orelse return error.MissingOffset; const desc = node.get_attribute("caption"); const parent_id = try db.get_peripheral_struct(peripheral_id); const struct_decl = try db.get_struct_decl_by_name(ctx.arena.allocator(), parent_id, name_in_module); @@ -1007,15 +978,11 @@ fn load_register_group_instance( try db.add_description(id, caption); // size is in bytes - if (node.get_attribute("size")) |size_str| { - const size = try std.fmt.parseInt(u64, size_str, 0); + if (try node.get_attribute_int(u64, "size")) |size| try db.add_size(id, size); - } - if (node.get_attribute("offset")) |offset_str| { - const offset = try std.fmt.parseInt(u64, offset_str, 0); + if (try node.get_attribute_int(u64, "offset")) |offset| try db.add_offset(id, offset); - } try db.add_child("instance.register_group", peripheral_id, id); } diff --git a/tools/regz/src/targetdb.zig b/tools/regz/src/targetdb.zig index 6ec7e05a4..989bc9456 100644 --- a/tools/regz/src/targetdb.zig +++ b/tools/regz/src/targetdb.zig @@ -86,10 +86,11 @@ fn load_device( const root = try doc.get_root_element(); - const cpu_node = root.find_child(&.{"cpu"}) orelse if (root.find_child(&.{"router"})) |router_node| blk: { + const cpu_node = root.find_child(&.{"cpu"}) orelse blk: { + const router_node = root.find_child(&.{"router"}) orelse return error.MissingField; const subpath_node = router_node.find_child(&.{"subpath"}) orelse return error.MissingField; break :blk subpath_node.find_child(&.{"cpu"}) orelse return error.MissingField; - } else return error.MissingField; + }; const isa_str = cpu_node.get_attribute("isa") orelse return error.MissingField; const arch = parse_isa_to_arch(isa_str); @@ -143,8 +144,7 @@ fn load_instance( const name = node.get_attribute("id") orelse return error.MissingField; const href = node.get_attribute("href") orelse return error.MissingField; - const baseaddr_str = node.get_attribute("baseaddr") orelse return error.MissingField; - const baseaddr = try std.fmt.parseInt(u64, baseaddr_str, 0); + const baseaddr = try node.get_attribute_int(u64, "baseaddr") orelse return error.MissingField; // Check if we've already loaded this module file const module_entry = if (modules.get(href)) |module_entry| blk: { @@ -167,10 +167,9 @@ fn load_instance( return; } - const description = root.get_attribute("description"); const new_peripheral_id = try db.create_peripheral(.{ .name = name, - .description = description, + .description = root.get_attribute("description"), }); // Detect format: check if first register has an offset attribute @@ -185,8 +184,7 @@ fn load_instance( var min_offset: ?u64 = null; register_it = root.iterate(&.{}, &.{"register"}); while (register_it.next()) |register_node| { - const offset_str = register_node.get_attribute("offset") orelse return error.MissingField; - const offset = try std.fmt.parseInt(u64, std.mem.trim(u8, offset_str, " "), 0); + const offset = try register_node.get_attribute_int(u64, "offset") orelse return error.MissingField; min_offset = if (min_offset) |current| @min(current, offset) else @@ -222,29 +220,23 @@ fn load_instance( }; // Create the device peripheral instance with the peripheral type - const description = node.get_attribute("description"); - const struct_id = try db.get_peripheral_struct(module_entry.peripheral_id); - _ = try db.create_device_peripheral(device_id, .{ .name = name, - .description = description, - .struct_id = struct_id, + .description = node.get_attribute("description"), + .struct_id = try db.get_peripheral_struct(module_entry.peripheral_id), .offset_bytes = baseaddr + module_entry.base_offset, }); } fn load_register(db: *Database, struct_id: StructID, base_offset: u64, node: xml.Node) !void { - const offset_str = node.get_attribute("offset") orelse return error.MissingField; - const abs_offset = try std.fmt.parseInt(u64, std.mem.trim(u8, offset_str, " "), 0); - - const width_str = node.get_attribute("width") orelse return error.MissingField; - const width_bits = try std.fmt.parseInt(u64, width_str, 0); + const abs_offset = try node.get_attribute_int(u64, "offset") orelse return error.MissingField; const register_id = try db.create_register(struct_id, .{ .name = node.get_attribute("acronym") orelse return error.MissingField, .description = node.get_attribute("description"), .offset_bytes = abs_offset - base_offset, - .size_bits = width_bits, + .size_bits = try node.get_attribute_int(u64, "width") orelse return error.MissingField, + .count = try node.get_attribute_int(u64, "instances"), }); var bitfield_it = node.iterate(&.{}, &.{"bitfield"}); @@ -254,8 +246,7 @@ fn load_register(db: *Database, struct_id: StructID, base_offset: u64, node: xml } fn load_register_sequential(db: *Database, struct_id: StructID, current_offset: *u64, node: xml.Node) !void { - const width_str = node.get_attribute("width") orelse return error.MissingField; - const width_bits = try std.fmt.parseInt(u64, width_str, 0); + const width_bits = try node.get_attribute_int(u64, "width") orelse return error.MissingField; const register_id = try db.create_register(struct_id, .{ .name = node.get_attribute("acronym") orelse return error.MissingField, @@ -277,11 +268,8 @@ fn load_bitfield(db: *Database, register_id: RegisterID, node: xml.Node) !void { const name = node.get_attribute("id") orelse return error.MissingField; const description = node.get_attribute("description"); - const width_str = node.get_attribute("width") orelse return error.MissingField; - const width_bits = try std.fmt.parseInt(u8, width_str, 0); - - const end_str = node.get_attribute("end") orelse return error.MissingField; - const end_bit = try std.fmt.parseInt(u8, end_str, 0); + const width_bits = try node.get_attribute_int(u8, "width") orelse return error.MissingField; + const end_bit = try node.get_attribute_int(u8, "end") orelse return error.MissingField; const access_str = node.get_attribute("rwaccess"); const access: Database.Access = if (access_str) |str| @@ -307,11 +295,10 @@ fn load_bitfield(db: *Database, register_id: RegisterID, node: xml.Node) !void { var bitenum_it = node.iterate(&.{}, &.{"bitenum"}); while (bitenum_it.next()) |bitenum_node| { - const value_str = bitenum_node.get_attribute("value") orelse return error.MissingField; try db.add_enum_field(enum_id, .{ .name = bitenum_node.get_attribute("id") orelse return error.MissingField, .description = bitenum_node.get_attribute("description"), - .value = try std.fmt.parseInt(u64, value_str, 0), + .value = try bitenum_node.get_attribute_int(u64, "value") orelse return error.MissingField, }); } diff --git a/tools/regz/src/xml.zig b/tools/regz/src/xml.zig index 500fb5dff..a1dbdf175 100644 --- a/tools/regz/src/xml.zig +++ b/tools/regz/src/xml.zig @@ -35,8 +35,8 @@ pub const Node = struct { attr: ?*Attr, pub const Entry = struct { - key: []const u8, - value: []const u8, + key: [:0]const u8, + value: [:0]const u8, }; pub fn next(it: *AttrIterator) ?Entry { @@ -56,11 +56,11 @@ pub const Node = struct { } }; - pub fn get_name(node: Node) []const u8 { + pub fn get_name(node: Node) [:0]const u8 { return std.mem.span(node.impl.name); } - pub fn get_attribute(node: Node, key: [:0]const u8) ?[]const u8 { + pub fn get_attribute(node: Node, key: [:0]const u8) ?[:0]const u8 { if (c.xmlHasProp(node.impl, key.ptr)) |prop| { if (@as(*c.xmlAttr, @ptrCast(prop)).children) |value_node| { if (@as(*c.xmlNode, @ptrCast(value_node)).content) |content| { @@ -72,6 +72,12 @@ pub const Node = struct { return null; } + pub fn get_attribute_int(node: Node, T: type, key: [:0]const u8) !?T { + const buf = get_attribute(node, key) orelse return null; + const buf_trim = std.mem.trim(u8, buf, &std.ascii.whitespace); + return std.fmt.parseInt(T, buf_trim, 0) catch |e| e; + } + pub fn find_child(node: Node, keys: []const []const u8) ?Node { var it = @as(?*c.xmlNode, @ptrCast(node.impl.children)); while (it != null) : (it = it.?.next) { From 18d249e80752adce20537b1a5dccad1d59332a7a Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Wed, 8 Jul 2026 03:25:31 +0200 Subject: [PATCH 02/17] centralized isa string to arch conversion --- tools/regz/src/arch.zig | 100 ++++++++++++++++++++++++++++++++++-- tools/regz/src/atdf.zig | 33 +----------- tools/regz/src/svd.zig | 63 +---------------------- tools/regz/src/targetdb.zig | 31 ++--------- 4 files changed, 103 insertions(+), 124 deletions(-) diff --git a/tools/regz/src/arch.zig b/tools/regz/src/arch.zig index d644f101e..9d01bd763 100644 --- a/tools/regz/src/arch.zig +++ b/tools/regz/src/arch.zig @@ -1,3 +1,5 @@ +const std = @import("std"); + // concrete arch's that we support in codegen, for stuff like interrupt // table generation pub const Arch = enum { @@ -52,10 +54,100 @@ pub const Arch = enum { pub const default = .unknown; pub fn to_string(arch: Arch) []const u8 { - return inline for (@typeInfo(Arch).@"enum".field_names) |field_name| { - if (@field(Arch, field_name) == arch) - break field_name; - } else unreachable; + return @tagName(arch); + } + + pub fn from_string(arch: []const u8) Arch { + // Convert ISA string (e.g., "CORTEX_M4", "MSP430") to lowercase and match to Arch enum + var buf: [32]u8 = undefined; + // Matches nothing, used to avoid duplicating log statement + var lower: []u8 = ""; + + if (arch.len <= buf.len) { + lower = std.ascii.lowerString(&buf, arch); + std.mem.replaceScalar(u8, lower, '-', '_'); + } + + // Try to match against all Arch enum values + return if (std.meta.stringToEnum(Arch, lower)) |ret| + ret + else if (std.mem.eql(u8, "armv8mml", lower)) + .arm_v81_mml + else if (std.mem.eql(u8, "armv8mbl", lower)) + .arm_v8_mbl + else if (std.mem.eql(u8, "armv81mml", lower)) + .arm_v8_mml + else if (std.mem.eql(u8, "avr8x_mega", lower)) + .avr8xmega + else if (std.mem.eql(u8, "cortex_m0p", lower)) + .cortex_m0plus + else if (std.mem.eql(u8, "cm0", lower)) + .cortex_m0 + else if (std.mem.eql(u8, "cm0plus", lower)) + .cortex_m0plus + else if (std.mem.eql(u8, "cm0+", lower)) + .cortex_m0plus + else if (std.mem.eql(u8, "cm1", lower)) + .cortex_m1 + else if (std.mem.eql(u8, "cm23", lower)) + .cortex_m23 + else if (std.mem.eql(u8, "cm3", lower)) + .cortex_m3 + else if (std.mem.eql(u8, "cm33", lower)) + .cortex_m33 + else if (std.mem.eql(u8, "cm35p", lower)) + .cortex_m35p + else if (std.mem.eql(u8, "cm4", lower)) + .cortex_m4 + else if (std.mem.eql(u8, "cm55", lower)) + .cortex_m55 + else if (std.mem.eql(u8, "cm7", lower)) + .cortex_m7 + else if (std.mem.eql(u8, "ca5", lower)) + .cortex_a5 + else if (std.mem.eql(u8, "ca7", lower)) + .cortex_a7 + else if (std.mem.eql(u8, "ca8", lower)) + .cortex_a8 + else if (std.mem.eql(u8, "ca9", lower)) + .cortex_a9 + else if (std.mem.eql(u8, "ca15", lower)) + .cortex_a15 + else if (std.mem.eql(u8, "ca17", lower)) + .cortex_a17 + else if (std.mem.eql(u8, "ca53", lower)) + .cortex_a53 + else if (std.mem.eql(u8, "ca57", lower)) + .cortex_a57 + else if (std.mem.eql(u8, "ca72", lower)) + .cortex_a72 + else if (std.mem.eql(u8, "qingkev2", lower)) + .qingke_v2 + else if (std.mem.eql(u8, "qingkev3", lower)) + .qingke_v3 + else if (std.mem.eql(u8, "qingkev4", lower)) + .qingke_v4 + else { + std.log.warn("Unknown Arch: {s}, defaulting to .unknown", .{arch}); + return .unknown; + }; + } + + pub fn is_cortex_m(arch: Arch) bool { + return switch (arch) { + .cortex_m0, + .cortex_m0plus, + .cortex_m1, + .cortex_m23, + .cortex_m3, + .cortex_m33, + .cortex_m35p, + .cortex_m4, + .cortex_m55, + .cortex_m7, + => true, + else => false, + }; } pub fn is_arm(arch: Arch) bool { diff --git a/tools/regz/src/atdf.zig b/tools/regz/src/atdf.zig index 11a37f00f..d57d4561d 100644 --- a/tools/regz/src/atdf.zig +++ b/tools/regz/src/atdf.zig @@ -69,15 +69,13 @@ fn load_device(ctx: *Context, node: xml.Node) !void { }); const name = node.get_attribute("name") orelse return error.NoDeviceName; - const arch_str = node.get_attribute("architecture") orelse return error.NoDeviceArch; - const arch = arch_from_str(arch_str); const family = node.get_attribute("family") orelse return error.NoDeviceFamily; const db = ctx.db; const device_id = try db.create_device(.{ .name = name, - .arch = arch, + .arch = .from_string(node.get_attribute("architecture") orelse return error.NoDeviceArch), }); try db.add_device_property(device_id, .{ .key = "family", .value = family }); @@ -147,35 +145,6 @@ fn load_interrupt_group(ctx: *Context, node: xml.Node, device_id: DeviceID) !voi } } -fn arch_from_str(str: []const u8) Arch { - return if (std.mem.eql(u8, "ARM926EJ-S", str)) - .arm926ej_s - else if (std.mem.eql(u8, "AVR8", str)) - .avr8 - else if (std.mem.eql(u8, "AVR8L", str)) - .avr8l - else if (std.mem.eql(u8, "AVR8X", str)) - .avr8x - else if (std.mem.eql(u8, "AVR8_XMEGA", str)) - .avr8xmega - else if (std.mem.eql(u8, "CORTEX-A5", str)) - .cortex_a5 - else if (std.mem.eql(u8, "CORTEX-A7", str)) - .cortex_a7 - else if (std.mem.eql(u8, "CORTEX-M0PLUS", str)) - .cortex_m0plus - else if (std.mem.eql(u8, "CORTEX-M23", str)) - .cortex_m23 - else if (std.mem.eql(u8, "CORTEX-M4", str)) - .cortex_m4 - else if (std.mem.eql(u8, "CORTEX-M7", str)) - .cortex_m7 - else if (std.mem.eql(u8, "MIPS", str)) - .mips - else - .unknown; -} - // This function is intended to normalize the struct layout of some peripheral // instances. Like in ATmega328P there will be register groups with offset of 0 // and then the registers themselves have their absolute offset. We'd like to diff --git a/tools/regz/src/svd.zig b/tools/regz/src/svd.zig index da6cfc0a6..d4f9a5f05 100644 --- a/tools/regz/src/svd.zig +++ b/tools/regz/src/svd.zig @@ -64,7 +64,7 @@ pub fn load_into_db(db: *Database, doc: xml.Doc) !void { var cpu_it = root.iterate(&.{}, &.{"cpu"}); if (cpu_it.next()) |cpu| { if (cpu.get_value("name")) |cpu_name| { - break :blk arch_from_str(cpu_name); + break :blk .from_string(cpu_name); } } @@ -204,67 +204,6 @@ fn derive_peripherals(ctx: *Context, device_id: DeviceID) !void { } } -fn arch_from_str(str: []const u8) Arch { - return if (std.mem.eql(u8, "CM0", str)) - .cortex_m0 - else if (std.mem.eql(u8, "CM0PLUS", str)) - .cortex_m0plus - else if (std.mem.eql(u8, "CM0+", str)) - .cortex_m0plus - else if (std.mem.eql(u8, "CM1", str)) - .cortex_m1 - else if (std.mem.eql(u8, "SC000", str)) - .sc000 - else if (std.mem.eql(u8, "CM23", str)) - .cortex_m23 - else if (std.mem.eql(u8, "CM3", str)) - .cortex_m3 - else if (std.mem.eql(u8, "CM33", str)) - .cortex_m33 - else if (std.mem.eql(u8, "CM35P", str)) - .cortex_m35p - else if (std.mem.eql(u8, "CM55", str)) - .cortex_m55 - else if (std.mem.eql(u8, "SC300", str)) - .sc300 - else if (std.mem.eql(u8, "CM4", str)) - .cortex_m4 - else if (std.mem.eql(u8, "CM7", str)) - .cortex_m7 - else if (std.mem.eql(u8, "ARMV8MML", str)) - .arm_v81_mml - else if (std.mem.eql(u8, "ARMV8MBL", str)) - .arm_v8_mbl - else if (std.mem.eql(u8, "ARMV81MML", str)) - .arm_v8_mml - else if (std.mem.eql(u8, "CA5", str)) - .cortex_a5 - else if (std.mem.eql(u8, "CA7", str)) - .cortex_a7 - else if (std.mem.eql(u8, "CA8", str)) - .cortex_a8 - else if (std.mem.eql(u8, "CA9", str)) - .cortex_a9 - else if (std.mem.eql(u8, "CA15", str)) - .cortex_a15 - else if (std.mem.eql(u8, "CA17", str)) - .cortex_a17 - else if (std.mem.eql(u8, "CA53", str)) - .cortex_a53 - else if (std.mem.eql(u8, "CA57", str)) - .cortex_a57 - else if (std.mem.eql(u8, "CA72", str)) - .cortex_a72 - else if (std.mem.eql(u8, "QINGKEV2", str)) - .qingke_v2 - else if (std.mem.eql(u8, "QINGKEV3", str)) - .qingke_v3 - else if (std.mem.eql(u8, "QINGKEV4", str)) - .qingke_v4 - else - .unknown; -} - pub fn load_peripheral(ctx: *Context, node: xml.Node, device_id: DeviceID) !void { const db = ctx.db; diff --git a/tools/regz/src/targetdb.zig b/tools/regz/src/targetdb.zig index 989bc9456..92dee832a 100644 --- a/tools/regz/src/targetdb.zig +++ b/tools/regz/src/targetdb.zig @@ -16,24 +16,6 @@ const ModuleEntry = struct { base_offset: u64, }; -fn parse_isa_to_arch(isa: []const u8) Arch { - // Convert ISA string (e.g., "CORTEX_M4", "MSP430") to lowercase and match to Arch enum - var buf: [32]u8 = undefined; - if (isa.len > buf.len) return .unknown; - - const lower = std.ascii.lowerString(&buf, isa); - - // Try to match against all Arch enum values - inline for (@typeInfo(Arch).@"enum".field_names) |field_name| { - if (std.mem.eql(u8, lower, field_name)) { - return @field(Arch, field_name); - } - } - - log.warn("Unknown ISA: {s}, defaulting to unknown arch", .{isa}); - return .unknown; -} - pub fn load_into_db(db: *Database, io: std.Io, path: []const u8, device: ?[]const u8) !void { var targetdb_dir = try std.Io.Dir.cwd().openDir(io, path, .{}); defer targetdb_dir.close(io); @@ -92,8 +74,7 @@ fn load_device( break :blk subpath_node.find_child(&.{"cpu"}) orelse return error.MissingField; }; - const isa_str = cpu_node.get_attribute("isa") orelse return error.MissingField; - const arch = parse_isa_to_arch(isa_str); + const arch: Arch = .from_string(cpu_node.get_attribute("isa") orelse return error.MissingField); const device_id = try db.create_device(.{ .arch = arch, @@ -108,15 +89,13 @@ fn load_device( _ = property_it; // Detect FPU presence for Cortex-M chips by checking for FPU instance - if (std.mem.startsWith(u8, isa_str, "CORTEX_M")) { + if (arch.is_cortex_m()) { var fpu_check_it = cpu_node.iterate(&.{}, &.{"instance"}); var has_fpu = false; while (fpu_check_it.next()) |instance_node| { - if (instance_node.get_attribute("id")) |id| { - if (std.mem.eql(u8, id, "FPU")) { - has_fpu = true; - break; - } + if (std.mem.eql(u8, "FPU", instance_node.get_attribute("id") orelse continue)) { + has_fpu = true; + break; } } From 38ecda0d0a96ce18dce04b27936f7274269d2d51 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Wed, 8 Jul 2026 17:25:18 +0200 Subject: [PATCH 03/17] very simple mspm0 example --- examples/texasinstruments/mspm0/README.md | 1 + examples/texasinstruments/mspm0/build.zig | 94 +++++++++++++++++++ examples/texasinstruments/mspm0/build.zig.zon | 15 +++ .../texasinstruments/mspm0/src/raw_blinky.zig | 30 ++++++ 4 files changed, 140 insertions(+) create mode 100644 examples/texasinstruments/mspm0/README.md create mode 100644 examples/texasinstruments/mspm0/build.zig create mode 100644 examples/texasinstruments/mspm0/build.zig.zon create mode 100644 examples/texasinstruments/mspm0/src/raw_blinky.zig diff --git a/examples/texasinstruments/mspm0/README.md b/examples/texasinstruments/mspm0/README.md new file mode 100644 index 000000000..cb2fb91e1 --- /dev/null +++ b/examples/texasinstruments/mspm0/README.md @@ -0,0 +1 @@ +# Texas Instructments MSPM0 diff --git a/examples/texasinstruments/mspm0/build.zig b/examples/texasinstruments/mspm0/build.zig new file mode 100644 index 000000000..6e0de6107 --- /dev/null +++ b/examples/texasinstruments/mspm0/build.zig @@ -0,0 +1,94 @@ +const std = @import("std"); +const microzig = @import("microzig"); + +const MicroBuild = microzig.MicroBuild(.{}); + +pub fn build(b: *std.Build) void { + const optimize = b.standardOptimizeOption(.{}); + + const mz_dep = b.dependency("microzig", .{}); + const mb = MicroBuild.init(b, mz_dep) orelse return; + + const reg_def = b.addWriteFiles().add("chip.zig", + \\const microzig = @import("microzig"); + \\ + \\pub const Properties = struct { + \\ has_vtor: ?bool = null, + \\ has_mpu: ?bool = null, + \\ has_fpu: ?bool = null, + \\ interrupt_priority_bits: ?u8 = null, + \\ dma_channel_count: ?u32 = null, + \\}; + \\ + \\pub const Interrupt = struct { + \\ name: [:0]const u8, + \\ index: i16, + \\ description: ?[:0]const u8, + \\}; + \\ + \\pub const properties: Properties = .{ + \\ .has_vtor = null, + \\ .has_mpu = null, + \\ .has_fpu = false, + \\ .interrupt_priority_bits = null, + \\ .dma_channel_count = null, + \\}; + \\ + \\pub const raw_properties = struct { + \\ pub const @"cpu.fpuPresent" = "false"; + \\}; + \\ + \\pub const interrupts: []const Interrupt = &.{ + \\ .{ .name = "NMI", .index = -14, .description = null }, + \\ .{ .name = "HardFault", .index = -13, .description = null }, + \\ .{ .name = "SVCall", .index = -5, .description = null }, + \\ .{ .name = "PendSV", .index = -2, .description = null }, + \\ .{ .name = "SysTick", .index = -1, .description = null }, + \\}; + \\ + \\pub const VectorTable = extern struct { + \\ const Handler = microzig.interrupt.Handler; + \\ const unhandled = microzig.interrupt.unhandled; + \\ + \\ initial_stack_pointer: *const anyopaque, + \\ Reset: Handler, + \\ NMI: Handler = unhandled, + \\ HardFault: Handler = unhandled, + \\ reserved2: [7]u32 = undefined, + \\ SVCall: Handler = unhandled, + \\ reserved10: [2]u32 = undefined, + \\ PendSV: Handler = unhandled, + \\ SysTick: Handler = unhandled, + \\}; + \\ + ); + + const chip = b.allocator.create(microzig.Target) catch @panic("OOM"); + chip.* = .{ + .dep = mz_dep, + .preferred_binary_format = .elf, + .zig_target = .{ + .cpu_arch = .thumb, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0plus }, + .os_tag = .freestanding, + .abi = .eabi, + }, + .chip = .{ + .name = "MSPM0C110X", + .register_definition = .{ .zig = reg_def }, + .memory_regions = comptime &.{ + .{ .tag = .flash, .offset = 0x0000_0000, .length = 8 * 1024, .access = .rx }, + .{ .tag = .ram, .offset = 0x2000_0000, .length = 1 * 1024, .access = .rwx }, + }, + }, + }; + + const raw_blinky = mb.add_firmware(.{ + .name = "raw_blinky_mspm0c110x", + .target = chip, + .optimize = optimize, + .root_source_file = b.path("src/raw_blinky.zig"), + }); + + mb.install_firmware(raw_blinky, .{}); +} diff --git a/examples/texasinstruments/mspm0/build.zig.zon b/examples/texasinstruments/mspm0/build.zig.zon new file mode 100644 index 000000000..459269c8f --- /dev/null +++ b/examples/texasinstruments/mspm0/build.zig.zon @@ -0,0 +1,15 @@ +.{ + .name = .mz_examples_ti_mspm0, + .fingerprint = 0x6dd3a36fb84aa34a, + .version = "0.0.1", + .minimum_zig_version = "0.17.0", + .dependencies = .{ + .microzig = .{ .path = "../../.." }, + }, + .paths = .{ + "src", + "build.zig", + "build.zig.zon", + "README.md", + }, +} diff --git a/examples/texasinstruments/mspm0/src/raw_blinky.zig b/examples/texasinstruments/mspm0/src/raw_blinky.zig new file mode 100644 index 000000000..28f90e6ed --- /dev/null +++ b/examples/texasinstruments/mspm0/src/raw_blinky.zig @@ -0,0 +1,30 @@ +const microzig = @import("microzig"); + +pub const panic = microzig.panic; +pub const std_options = microzig.std_options(.{}); +comptime { + _ = microzig.export_startup(); +} + +pub fn main() void { + const pin = 22; + + const IOMUX_BASE: usize = 0x40428000; + const GPIO0_BASE: usize = 0x400A0000; + const PINCM: *usize = @ptrFromInt(IOMUX_BASE + (pin + 1) * 4); + const GPIO_DOE31_0: *usize = @ptrFromInt(GPIO0_BASE + 0x12c0); + const GPIO_DOUTTGL31_0: *usize = @ptrFromInt(GPIO0_BASE + 0x12b0); + const PIN_MASK: usize = (1 << pin); + const GPIO0_PWREN: *usize = @ptrFromInt(GPIO0_BASE + 0x800); + + GPIO0_PWREN.* = 0x26000001; + PINCM.* = (1 << 0) | (1 << 7); + GPIO_DOE31_0.* |= PIN_MASK; + + while (true) { + GPIO_DOUTTGL31_0.* = PIN_MASK; + + for (0..2_000_000) |_| + asm volatile ("nop"); + } +} From 43573c0f7444c6f380290645367766de13116760 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Wed, 8 Jul 2026 19:46:56 +0200 Subject: [PATCH 04/17] Add MSPM0 port --- build.zig | 2 + build.zig.zon | 1 + examples/texasinstruments/mspm0/build.zig | 92 ++---------- port/texasinstruments/mspm0/README.md | 1 + port/texasinstruments/mspm0/build.zig | 142 +++++++++++++++++++ port/texasinstruments/mspm0/build.zig.zon | 21 +++ port/texasinstruments/mspm0/patches/gpio.zon | 23 +++ port/texasinstruments/mspm0/src/hal.zig | 1 + port/texasinstruments/mspm0/src/hal/gpio.zig | 72 ++++++++++ tools/regz/src/targetdb.zig | 35 ++--- 10 files changed, 289 insertions(+), 101 deletions(-) create mode 100644 port/texasinstruments/mspm0/README.md create mode 100644 port/texasinstruments/mspm0/build.zig create mode 100644 port/texasinstruments/mspm0/build.zig.zon create mode 100644 port/texasinstruments/mspm0/patches/gpio.zon create mode 100644 port/texasinstruments/mspm0/src/hal.zig create mode 100644 port/texasinstruments/mspm0/src/hal/gpio.zig diff --git a/build.zig b/build.zig index 9c518c7b9..f83653cf6 100644 --- a/build.zig +++ b/build.zig @@ -32,6 +32,7 @@ const port_list: []const struct { .{ .name = "stm32", .dep_name = "port/stmicro/stm32" }, .{ .name = "ch32v", .dep_name = "port/wch/ch32v" }, .{ .name = "msp430", .dep_name = "port/texasinstruments/msp430" }, + .{ .name = "mspm0", .dep_name = "port/texasinstruments/mspm0" }, .{ .name = "tm4c", .dep_name = "port/texasinstruments/tm4c" }, }; @@ -70,6 +71,7 @@ pub const PortSelect = struct { stm32: bool = false, ch32v: bool = false, msp430: bool = false, + mspm0: bool = false, tm4c: bool = false, pub const all: PortSelect = blk: { diff --git a/build.zig.zon b/build.zig.zon index 0dd082589..6b2515b59 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -40,6 +40,7 @@ .@"port/raspberrypi/rp2xxx" = .{ .path = "port/raspberrypi/rp2xxx", .lazy = true }, .@"port/stmicro/stm32" = .{ .path = "port/stmicro/stm32", .lazy = true }, .@"port/texasinstruments/msp430" = .{ .path = "port/texasinstruments/msp430", .lazy = true }, + .@"port/texasinstruments/mspm0" = .{ .path = "port/texasinstruments/mspm0", .lazy = true }, .@"port/texasinstruments/tm4c" = .{ .path = "port/texasinstruments/tm4c", .lazy = true }, .@"port/wch/ch32v" = .{ .path = "port/wch/ch32v", .lazy = true }, }, diff --git a/examples/texasinstruments/mspm0/build.zig b/examples/texasinstruments/mspm0/build.zig index 6e0de6107..d9b9ecaa0 100644 --- a/examples/texasinstruments/mspm0/build.zig +++ b/examples/texasinstruments/mspm0/build.zig @@ -1,7 +1,7 @@ const std = @import("std"); const microzig = @import("microzig"); -const MicroBuild = microzig.MicroBuild(.{}); +const MicroBuild = microzig.MicroBuild(.{ .mspm0 = true }); pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); @@ -9,86 +9,16 @@ pub fn build(b: *std.Build) void { const mz_dep = b.dependency("microzig", .{}); const mb = MicroBuild.init(b, mz_dep) orelse return; - const reg_def = b.addWriteFiles().add("chip.zig", - \\const microzig = @import("microzig"); - \\ - \\pub const Properties = struct { - \\ has_vtor: ?bool = null, - \\ has_mpu: ?bool = null, - \\ has_fpu: ?bool = null, - \\ interrupt_priority_bits: ?u8 = null, - \\ dma_channel_count: ?u32 = null, - \\}; - \\ - \\pub const Interrupt = struct { - \\ name: [:0]const u8, - \\ index: i16, - \\ description: ?[:0]const u8, - \\}; - \\ - \\pub const properties: Properties = .{ - \\ .has_vtor = null, - \\ .has_mpu = null, - \\ .has_fpu = false, - \\ .interrupt_priority_bits = null, - \\ .dma_channel_count = null, - \\}; - \\ - \\pub const raw_properties = struct { - \\ pub const @"cpu.fpuPresent" = "false"; - \\}; - \\ - \\pub const interrupts: []const Interrupt = &.{ - \\ .{ .name = "NMI", .index = -14, .description = null }, - \\ .{ .name = "HardFault", .index = -13, .description = null }, - \\ .{ .name = "SVCall", .index = -5, .description = null }, - \\ .{ .name = "PendSV", .index = -2, .description = null }, - \\ .{ .name = "SysTick", .index = -1, .description = null }, - \\}; - \\ - \\pub const VectorTable = extern struct { - \\ const Handler = microzig.interrupt.Handler; - \\ const unhandled = microzig.interrupt.unhandled; - \\ - \\ initial_stack_pointer: *const anyopaque, - \\ Reset: Handler, - \\ NMI: Handler = unhandled, - \\ HardFault: Handler = unhandled, - \\ reserved2: [7]u32 = undefined, - \\ SVCall: Handler = unhandled, - \\ reserved10: [2]u32 = undefined, - \\ PendSV: Handler = unhandled, - \\ SysTick: Handler = unhandled, - \\}; - \\ - ); + inline for (comptime std.meta.fieldNames(@TypeOf(mb.ports.mspm0.chips))) |field_name| { + const target = @field(mb.ports.mspm0.chips, field_name); - const chip = b.allocator.create(microzig.Target) catch @panic("OOM"); - chip.* = .{ - .dep = mz_dep, - .preferred_binary_format = .elf, - .zig_target = .{ - .cpu_arch = .thumb, - .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0plus }, - .os_tag = .freestanding, - .abi = .eabi, - }, - .chip = .{ - .name = "MSPM0C110X", - .register_definition = .{ .zig = reg_def }, - .memory_regions = comptime &.{ - .{ .tag = .flash, .offset = 0x0000_0000, .length = 8 * 1024, .access = .rx }, - .{ .tag = .ram, .offset = 0x2000_0000, .length = 1 * 1024, .access = .rwx }, - }, - }, - }; + const raw_blinky = mb.add_firmware(.{ + .name = b.fmt("raw_blinky_{s}", .{field_name}), + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/raw_blinky.zig"), + }); - const raw_blinky = mb.add_firmware(.{ - .name = "raw_blinky_mspm0c110x", - .target = chip, - .optimize = optimize, - .root_source_file = b.path("src/raw_blinky.zig"), - }); - - mb.install_firmware(raw_blinky, .{}); + mb.install_firmware(raw_blinky, .{}); + } } diff --git a/port/texasinstruments/mspm0/README.md b/port/texasinstruments/mspm0/README.md new file mode 100644 index 000000000..cb2fb91e1 --- /dev/null +++ b/port/texasinstruments/mspm0/README.md @@ -0,0 +1 @@ +# Texas Instructments MSPM0 diff --git a/port/texasinstruments/mspm0/build.zig b/port/texasinstruments/mspm0/build.zig new file mode 100644 index 000000000..f1e12c576 --- /dev/null +++ b/port/texasinstruments/mspm0/build.zig @@ -0,0 +1,142 @@ +const std = @import("std"); +const microzig = @import("microzig/build-internals"); + +chips: Chips, +boards: Boards, + +pub const ChipVariant = struct { + name: []const u8, + flash_kb: u32, + sram_kb: u32, +}; + +// Maybe somehow get the ram and flash size automatically in the future? +// Flash size seems too be (1 << last_name_digit) KiB +pub const chip_variants: []const ChipVariant = &.{ + // C series + // .{ .name = "MSPM0C1103", .flash_kb = 8, .sram_kb = 1 }, + .{ .name = "MSPM0C1104", .flash_kb = 16, .sram_kb = 1 }, + // .{ .name = "MSPM0C1105", .flash_kb = 32, .sram_kb = 8 }, + // .{ .name = "MSPM0C1106", .flash_kb = 64, .sram_kb = 8 }, + + // G series + // .{ .name = "MSPM0G1105", .flash_kb = 32, .sram_kb = }, + // .{ .name = "MSPM0G1106", .flash_kb = 64, .sram_kb = }, + // .{ .name = "MSPM0G1107", .flash_kb = 128, .sram_kb = }, + // .{ .name = "MSPM0G1207", .flash_kb = 128, .sram_kb = }, + // .{ .name = "MSPM0G1218", .flash_kb = 256, .sram_kb = }, + // .{ .name = "MSPM0G1505", .flash_kb = 32, .sram_kb = }, + // .{ .name = "MSPM0G1506", .flash_kb = 64, .sram_kb = }, + // .{ .name = "MSPM0G1507", .flash_kb = 128, .sram_kb = }, + // .{ .name = "MSPM0G1518", .flash_kb = 256, .sram_kb = }, + // .{ .name = "MSPM0G1519", .flash_kb = 512, .sram_kb = }, + // .{ .name = "MSPM0G3105", .flash_kb = 32, .sram_kb = }, + // .{ .name = "MSPM0G3106", .flash_kb = 64, .sram_kb = }, + // .{ .name = "MSPM0G3107", .flash_kb = 128, .sram_kb = }, + // .{ .name = "MSPM0G3207", .flash_kb = 128, .sram_kb = }, + // .{ .name = "MSPM0G3218", .flash_kb = 256, .sram_kb = }, + // .{ .name = "MSPM0G3505", .flash_kb = 32, .sram_kb = }, + // .{ .name = "MSPM0G3506", .flash_kb = 64, .sram_kb = }, + // .{ .name = "MSPM0G3507", .flash_kb = 128, .sram_kb = }, + // .{ .name = "MSPM0G3518", .flash_kb = 256, .sram_kb = }, + // .{ .name = "MSPM0G3519", .flash_kb = 512, .sram_kb = }, + // .{ .name = "MSPM0G3529", .flash_kb = 512, .sram_kb = }, + // .{ .name = "MSPM0G5115", .flash_kb = 32, .sram_kb = }, + // .{ .name = "MSPM0G5116", .flash_kb = 64, .sram_kb = }, + // .{ .name = "MSPM0G5117", .flash_kb = 128, .sram_kb = }, + // .{ .name = "MSPM0G5187", .flash_kb = 128, .sram_kb = }, + + // H series + // .{ .name = "MSPM0H3215", .flash_kb = 32, .sram_kb = }, + // .{ .name = "MSPM0H3216", .flash_kb = 64, .sram_kb = 8 }, + + // L series + // .{ .name = "MSPM0L1105", .flash_kb = 32, .sram_kb = 4 }, + // .{ .name = "MSPM0L1106", .flash_kb = 64, .sram_kb = 4 }, + // .{ .name = "MSPM0L1116", .flash_kb = 64, .sram_kb = 16 }, + // .{ .name = "MSPM0L1117", .flash_kb = 128, .sram_kb = 16 }, + // .{ .name = "MSPM0L1126", .flash_kb = 64, .sram_kb = }, + // .{ .name = "MSPM0L1127", .flash_kb = 128, .sram_kb = }, + // .{ .name = "MSPM0L1227", .flash_kb = 128, .sram_kb = }, + // .{ .name = "MSPM0L1228", .flash_kb = 256, .sram_kb = }, + // .{ .name = "MSPM0L1303", .flash_kb = 8, .sram_kb = 2 }, + // .{ .name = "MSPM0L1304", .flash_kb = 16, .sram_kb = 2 }, + // .{ .name = "MSPM0L1305", .flash_kb = 32, .sram_kb = 4 }, + // .{ .name = "MSPM0L1306", .flash_kb = 64, .sram_kb = 4 }, + // .{ .name = "MSPM0L1343", .flash_kb = 8, .sram_kb = }, + // .{ .name = "MSPM0L1344", .flash_kb = 16, .sram_kb = }, + // .{ .name = "MSPM0L1345", .flash_kb = 32, .sram_kb = }, + // .{ .name = "MSPM0L1346", .flash_kb = 64, .sram_kb = }, + // .{ .name = "MSPM0L2104", .flash_kb = 16, .sram_kb = }, + // .{ .name = "MSPM0L2105", .flash_kb = 32, .sram_kb = }, + // .{ .name = "MSPM0L2116", .flash_kb = 64, .sram_kb = }, + // .{ .name = "MSPM0L2117", .flash_kb = 128, .sram_kb = }, + // .{ .name = "MSPM0L2227", .flash_kb = 128, .sram_kb = }, + // .{ .name = "MSPM0L2228", .flash_kb = 256, .sram_kb = }, +}; + +pub const Chips = @Struct( + .auto, + null, + blk: { + var ret: [chip_variants.len][]const u8 = undefined; + for (chip_variants, 0..) |v, i| ret[i] = v.name; + break :blk &ret; + }, + &@splat(*microzig.Target), + &@splat(.{}), +); + +pub const Boards = struct {}; + +pub fn init(dep: *std.Build.Dependency) ?@This() { + const b = dep.builder; + const ti_data = b.lazyDependency("ti_data", .{}) orelse return null; + const targetdb = ti_data.path("targetdb"); + + const patch_paths = [_][]const u8{ + "patches/gpio.zon", + }; + const patch_files = b.allocator.alloc(std.Build.LazyPath, patch_paths.len) catch @panic("OOM"); + for (patch_files, patch_paths) |*dst, src| + dst.* = b.path(src); + + var chips: Chips = undefined; + + inline for (chip_variants) |v| { + const chip = b.allocator.create(microzig.Target) catch @panic("OOM"); + chip.* = .{ + .dep = dep, + .preferred_binary_format = .elf, + .zig_target = .{ + .cpu_arch = .thumb, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0plus }, + .os_tag = .freestanding, + .abi = .eabi, + }, + .chip = .{ + .name = v.name, + .register_definition = .{ .targetdb = .{ + .path = targetdb, + .device = v.name, + } }, + .memory_regions = comptime &.{ + .{ .tag = .flash, .offset = 0x0000_0000, .length = v.flash_kb * 1024, .access = .rx }, + .{ .tag = .ram, .offset = 0x2000_0000, .length = v.sram_kb * 1024, .access = .rwx }, + }, + .patch_files = patch_files, + }, + .hal = .{ .root_source_file = dep.builder.path("src/hal.zig") }, + }; + @field(chips, v.name) = chip; + } + + return .{ + .chips = chips, + .boards = .{}, + }; +} + +pub fn build(b: *std.Build) !void { + _ = b.step("test", "Run platform agnostic unit tests"); +} diff --git a/port/texasinstruments/mspm0/build.zig.zon b/port/texasinstruments/mspm0/build.zig.zon new file mode 100644 index 000000000..5cd4a19f5 --- /dev/null +++ b/port/texasinstruments/mspm0/build.zig.zon @@ -0,0 +1,21 @@ +.{ + .name = .mz_port_texasinstruments_mspm0, + .fingerprint = 0x532b24ad1c2b55f3, + .version = "0.0.1", + .minimum_zig_version = "0.17.0", + .dependencies = .{ + .@"microzig/build-internals" = .{ .path = "../../../build-internals" }, + .@"microzig/tools/regz" = .{ .path = "../../../tools/regz" }, + .ti_data = .{ + .url = "git+https://github.com/ZigEmbeddedGroup/texasinstruments-data.git#2bdd0ff770ea4070caf25e2d33e9744374c75b21", + .hash = "N-V-__8AAEUAg0HJNVCziuaLjHq479tsKsXb4GbNf5ye38oi", + .lazy = true, + }, + }, + .paths = .{ + "src", + "build.zig", + "build.zig.zon", + "README.md", + }, +} diff --git a/port/texasinstruments/mspm0/patches/gpio.zon b/port/texasinstruments/mspm0/patches/gpio.zon new file mode 100644 index 000000000..c3b9aec62 --- /dev/null +++ b/port/texasinstruments/mspm0/patches/gpio.zon @@ -0,0 +1,23 @@ +.{ + .{ + .add_enum_and_apply = .{ + .parent = "types.peripherals.gpioa", + .@"enum" = .{ + .name = "ChannelID", + .bitsize = 2, + .fields = .{ + .{ .name = "none", .value = 0 }, + .{ .name = "ch_1", .value = 1 }, + .{ .name = "ch_2", .value = 2 }, + .{ .name = "ch_3", .value = 3 }, + }, + }, + .apply_to = .{ + "types.peripherals.gpioa.GPIOA_FSUB_0.CHANID", + "types.peripherals.gpioa.GPIOA_FSUB_1.CHANID", + "types.peripherals.gpioa.GPIOA_FPUB_0.CHANID", + "types.peripherals.gpioa.GPIOA_FPUB_1.CHANID", + }, + }, + }, +} diff --git a/port/texasinstruments/mspm0/src/hal.zig b/port/texasinstruments/mspm0/src/hal.zig new file mode 100644 index 000000000..6b50631c2 --- /dev/null +++ b/port/texasinstruments/mspm0/src/hal.zig @@ -0,0 +1 @@ +pub const gpio = @import("hal/gpio.zig"); diff --git a/port/texasinstruments/mspm0/src/hal/gpio.zig b/port/texasinstruments/mspm0/src/hal/gpio.zig new file mode 100644 index 000000000..33b005c18 --- /dev/null +++ b/port/texasinstruments/mspm0/src/hal/gpio.zig @@ -0,0 +1,72 @@ +const std = @import("std"); +const microzig = @import("microzig"); + +const peri = microzig.chip.peripherals; + +pub const Port = enum { gpioa }; + +fn instance(port: Port) *volatile microzig.chip.types.peripherals.gpioa { + return switch (port) { + inline else => @field(peri, @tagName(port)), + }; +} + +pub fn enable(port: Port) void { + instance(port).GPIOA_PWREN.raw = 0x26000001; +} + +pub const Direction = enum(u1) { in, out }; + +pub const Pin = struct { + port: Port, + num: u5, + + pub fn set_function(p: Pin, function: u6) void { + peri.iomux.IOMUX_PINCM[p.num].write(.{ + .PF = @enumFromInt(function), + .PC = .CONNECTED, + .WAKESTAT = .DISABLE, + .PIPD = .DISABLE, + .PIPU = .DISABLE, + .INENA = .DISABLE, + .HYSTEN = .DISABLE, + .DRV = .DRVVAL0, + .HIZ1 = .DISABLE, + .INV = .DISABLE, + .WUEN = .DISABLE, + .WCOMP = .MATCH0, + }); + } + + pub fn set_direction(p: Pin, dir: Direction) void { + const reg = switch (dir) { + .in => &instance(p.port).GPIOA_DOECLR31_0.raw, + .out => &instance(p.port).GPIOA_DOESET31_0.raw, + }; + reg.* = p.mask(); + } + + pub fn mask(p: Pin) u32 { + return @as(u32, 1) << p.num; + } + + pub fn put(p: Pin, value: u1) void { + const reg = switch (value) { + 0 => &instance(p.port).GPIOA_DOUTCLR31_0.raw, + 1 => &instance(p.port).GPIOA_DOUTSET31_0.raw, + }; + reg.* = p.mask(); + } + + pub fn toggle(p: Pin) void { + instance(p.port).GPIOA_DOUTTGL31_0.raw = p.mask(); + } + + pub fn read(p: Pin) u1 { + return @intFromBool(instance(p.port).GPIOA_DIN31_0.raw & p.mask() != 0); + } +}; + +pub fn num(port: Port, n: u5) Pin { + return .{ .port = port, .num = n }; +} diff --git a/tools/regz/src/targetdb.zig b/tools/regz/src/targetdb.zig index 92dee832a..710ea3ee0 100644 --- a/tools/regz/src/targetdb.zig +++ b/tools/regz/src/targetdb.zig @@ -37,7 +37,9 @@ pub fn load_into_db(db: *Database, io: std.Io, path: []const u8, device: ?[]cons if (entry.kind != .file) continue; - if (!std.mem.startsWith(u8, entry.name, "MSP430") and !std.mem.startsWith(u8, entry.name, "tm4c")) + if (!std.mem.startsWith(u8, entry.name, "MSP430") and + !std.mem.startsWith(u8, entry.name, "MSPM0") and + !std.mem.startsWith(u8, entry.name, "tm4c")) continue; if (device) |d| { @@ -153,28 +155,19 @@ fn load_instance( // Detect format: check if first register has an offset attribute var register_it = root.iterate(&.{}, &.{"register"}); - const has_explicit_offsets = if (register_it.next()) |first_register| - first_register.get_attribute("offset") != null - else - false; - - const base_offset: u64 = if (has_explicit_offsets) base_blk: { - // Original format: registers have explicit offset attributes - var min_offset: ?u64 = null; - register_it = root.iterate(&.{}, &.{"register"}); + const base_offset_opt: ?u64 = if (register_it.next()) |first_register| base_blk: { + var min_offset: u64 = try first_register.get_attribute_int(u64, "offset") orelse break :base_blk null; while (register_it.next()) |register_node| { - const offset = try register_node.get_attribute_int(u64, "offset") orelse return error.MissingField; - min_offset = if (min_offset) |current| - @min(current, offset) - else - offset; + const offset = try register_node.get_attribute_int(u64, "offset") orelse + return error.MissingField; + min_offset = @min(min_offset, offset); } - break :base_blk min_offset orelse return error.MissingField; - } else 0; + break :base_blk min_offset; + } else null; const struct_id = try db.get_peripheral_struct(new_peripheral_id); - if (has_explicit_offsets) { + if (base_offset_opt) |base_offset| { register_it = root.iterate(&.{}, &.{"register"}); while (register_it.next()) |register_node| { try load_register(db, struct_id, base_offset, register_node); @@ -190,7 +183,7 @@ fn load_instance( // Cache the peripheral type and base offset for this module file const new_module_entry = ModuleEntry{ .peripheral_id = new_peripheral_id, - .base_offset = base_offset, + .base_offset = @intCast(base_offset_opt orelse 0), }; const href_copy = try db.gpa.dupe(u8, href); try modules.put(href_copy, new_module_entry); @@ -211,7 +204,9 @@ fn load_register(db: *Database, struct_id: StructID, base_offset: u64, node: xml const abs_offset = try node.get_attribute_int(u64, "offset") orelse return error.MissingField; const register_id = try db.create_register(struct_id, .{ - .name = node.get_attribute("acronym") orelse return error.MissingField, + .name = node.get_attribute("acronym") orelse + node.get_attribute("id") orelse + return error.MissingField, .description = node.get_attribute("description"), .offset_bytes = abs_offset - base_offset, .size_bits = try node.get_attribute_int(u64, "width") orelse return error.MissingField, From e12ce97840a68d1562c6511988416c704b6fe79d Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 9 Jul 2026 01:26:19 +0200 Subject: [PATCH 05/17] Better blinky and register group support --- examples/texasinstruments/mspm0/build.zig | 9 ++++ .../texasinstruments/mspm0/src/blinky.zig | 25 +++++++++ tools/regz/src/targetdb.zig | 54 +++++++++---------- 3 files changed, 58 insertions(+), 30 deletions(-) create mode 100644 examples/texasinstruments/mspm0/src/blinky.zig diff --git a/examples/texasinstruments/mspm0/build.zig b/examples/texasinstruments/mspm0/build.zig index d9b9ecaa0..e21ae8663 100644 --- a/examples/texasinstruments/mspm0/build.zig +++ b/examples/texasinstruments/mspm0/build.zig @@ -20,5 +20,14 @@ pub fn build(b: *std.Build) void { }); mb.install_firmware(raw_blinky, .{}); + + const blinky = mb.add_firmware(.{ + .name = b.fmt("blinky_{s}", .{field_name}), + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/blinky.zig"), + }); + + mb.install_firmware(blinky, .{}); } } diff --git a/examples/texasinstruments/mspm0/src/blinky.zig b/examples/texasinstruments/mspm0/src/blinky.zig new file mode 100644 index 000000000..a39d7f4ea --- /dev/null +++ b/examples/texasinstruments/mspm0/src/blinky.zig @@ -0,0 +1,25 @@ +// const std = @import("std"); +const microzig = @import("microzig"); +const mspm0 = microzig.hal; + +pub const panic = microzig.panic; +pub const std_options = microzig.std_options(.{}); +comptime { + _ = microzig.export_startup(); +} + +pub fn main() void { + mspm0.gpio.enable(.gpioa); + + const led = mspm0.gpio.num(.gpioa, 22); + + led.set_function(1); + led.set_direction(.out); + + while (true) { + led.toggle(); + + for (0..2_000_000) |_| + asm volatile ("nop"); + } +} diff --git a/tools/regz/src/targetdb.zig b/tools/regz/src/targetdb.zig index 710ea3ee0..0b2b202b7 100644 --- a/tools/regz/src/targetdb.zig +++ b/tools/regz/src/targetdb.zig @@ -154,11 +154,11 @@ fn load_instance( }); // Detect format: check if first register has an offset attribute - var register_it = root.iterate(&.{}, &.{"register"}); - const base_offset_opt: ?u64 = if (register_it.next()) |first_register| base_blk: { - var min_offset: u64 = try first_register.get_attribute_int(u64, "offset") orelse break :base_blk null; + var register_it = root.iterate(&.{}, &.{ "register", "group" }); + const base_offset_opt: ?u63 = if (register_it.next()) |first_register| base_blk: { + var min_offset: u63 = try first_register.get_attribute_int(u63, "offset") orelse break :base_blk null; while (register_it.next()) |register_node| { - const offset = try register_node.get_attribute_int(u64, "offset") orelse + const offset = try register_node.get_attribute_int(u63, "offset") orelse return error.MissingField; min_offset = @min(min_offset, offset); } @@ -168,15 +168,23 @@ fn load_instance( const struct_id = try db.get_peripheral_struct(new_peripheral_id); if (base_offset_opt) |base_offset| { + var group_it = root.iterate(&.{}, &.{"group"}); register_it = root.iterate(&.{}, &.{"register"}); - while (register_it.next()) |register_node| { - try load_register(db, struct_id, base_offset, register_node); + var group_offset: i64 = 0; + + while (true) { + while (register_it.next()) |register_node| { + _ = try load_register(db, struct_id, group_offset - @as(i64, base_offset), null, register_node); + } + const group = group_it.next() orelse break; + group_offset = try group.get_attribute_int(u63, "offset") orelse return error.MissingField; + register_it = group.iterate(&.{}, &.{"register"}); } } else { - var current_offset: u64 = 0; + var next_offset: u63 = 0; register_it = root.iterate(&.{}, &.{"register"}); while (register_it.next()) |register_node| { - try load_register_sequential(db, struct_id, ¤t_offset, register_node); + next_offset += try load_register(db, struct_id, 0, next_offset, register_node); } } @@ -200,42 +208,28 @@ fn load_instance( }); } -fn load_register(db: *Database, struct_id: StructID, base_offset: u64, node: xml.Node) !void { - const abs_offset = try node.get_attribute_int(u64, "offset") orelse return error.MissingField; +fn load_register(db: *Database, struct_id: StructID, base_offset: i64, next_offset: ?u63, node: xml.Node) !u63 { + const offset = next_offset orelse try node.get_attribute_int(u63, "offset") orelse return error.MissingField; + const width_bits = try node.get_attribute_int(u63, "width") orelse return error.MissingField; + const count = try node.get_attribute_int(u63, "instances"); const register_id = try db.create_register(struct_id, .{ .name = node.get_attribute("acronym") orelse node.get_attribute("id") orelse return error.MissingField, .description = node.get_attribute("description"), - .offset_bytes = abs_offset - base_offset, - .size_bits = try node.get_attribute_int(u64, "width") orelse return error.MissingField, - .count = try node.get_attribute_int(u64, "instances"), + .offset_bytes = std.math.cast(u64, base_offset + @as(i64, offset)) orelse return error.NegativeOffset, + .size_bits = width_bits, + .count = count orelse null, }); var bitfield_it = node.iterate(&.{}, &.{"bitfield"}); while (bitfield_it.next()) |bitfield_node| { try load_bitfield(db, register_id, bitfield_node); } -} - -fn load_register_sequential(db: *Database, struct_id: StructID, current_offset: *u64, node: xml.Node) !void { - const width_bits = try node.get_attribute_int(u64, "width") orelse return error.MissingField; - - const register_id = try db.create_register(struct_id, .{ - .name = node.get_attribute("acronym") orelse return error.MissingField, - .description = node.get_attribute("description"), - .offset_bytes = current_offset.*, - .size_bits = width_bits, - }); // Update offset for next register (width in bits converted to bytes) - current_offset.* += width_bits / 8; - - var bitfield_it = node.iterate(&.{}, &.{"bitfield"}); - while (bitfield_it.next()) |bitfield_node| { - try load_bitfield(db, register_id, bitfield_node); - } + return (count orelse 1) * try std.math.divExact(u63, width_bits, 8); } fn load_bitfield(db: *Database, register_id: RegisterID, node: xml.Node) !void { From 54d444edcdd240344e10c1d84fd5dbca6fa5f93c Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 12 Jul 2026 23:20:33 +0200 Subject: [PATCH 06/17] regz: extend arch.to_string to work with embassy parser --- tools/regz/src/arch.zig | 2 ++ tools/regz/src/embassy.zig | 11 +---------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/tools/regz/src/arch.zig b/tools/regz/src/arch.zig index 9d01bd763..2ca9d1afa 100644 --- a/tools/regz/src/arch.zig +++ b/tools/regz/src/arch.zig @@ -85,6 +85,8 @@ pub const Arch = enum { .cortex_m0 else if (std.mem.eql(u8, "cm0plus", lower)) .cortex_m0plus + else if (std.mem.eql(u8, "cm0p", lower)) + .cortex_m0plus else if (std.mem.eql(u8, "cm0+", lower)) .cortex_m0plus else if (std.mem.eql(u8, "cm1", lower)) diff --git a/tools/regz/src/embassy.zig b/tools/regz/src/embassy.zig index d736f6f8d..8efd2b6af 100644 --- a/tools/regz/src/embassy.zig +++ b/tools/regz/src/embassy.zig @@ -3,15 +3,6 @@ const Database = @import("Database.zig"); const Arch = @import("arch.zig").Arch; const arm = @import("arch/arm.zig"); -pub const core_to_cpu = std.StaticStringMap([]const u8).initComptime(&.{ - .{ "cm0", "cortex_m0" }, - .{ "cm0p", "cortex_m0plus" }, - .{ "cm3", "cortex_m3" }, - .{ "cm4", "cortex_m4" }, - .{ "cm7", "cortex_m7" }, - .{ "cm33", "cortex_m33" }, -}); - pub const ChipFile = struct { name: []const u8, family: []const u8, @@ -530,7 +521,7 @@ pub fn load_into_db(db: *Database, io: std.Io, path: []const u8, device: ?[]cons for (chip_files.items) |chip_file| { const core = chip_file.value.cores[0]; - const arch = std.meta.stringToEnum(Arch, core_to_cpu.get(core.name).?).?; + const arch: Arch = .from_string(core.name); const device_id = try db.create_device(.{ .name = chip_file.value.name, .arch = arch, From a453476fe2122d11ee121eaee4738deab088442b Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 12 Jul 2026 23:27:50 +0200 Subject: [PATCH 07/17] regz: unify xml BitRange parsing --- tools/regz/src/BitRange.zig | 49 +++++++++++++++++++++++++++++ tools/regz/src/svd.zig | 61 +++---------------------------------- tools/regz/src/targetdb.zig | 10 +++--- 3 files changed, 59 insertions(+), 61 deletions(-) create mode 100644 tools/regz/src/BitRange.zig diff --git a/tools/regz/src/BitRange.zig b/tools/regz/src/BitRange.zig new file mode 100644 index 000000000..1c25c001f --- /dev/null +++ b/tools/regz/src/BitRange.zig @@ -0,0 +1,49 @@ +const std = @import("std"); +const xml = @import("xml"); + +const BitRange = @This(); + +offset: u8, +width: u8, + +pub fn lsb_msb(lsb: u8, msb: u8) !BitRange { + return if (msb < lsb) + error.InvalidRange + else + .{ + .offset = lsb, + .width = msb - lsb + 1, + }; +} + +pub fn parse_xml(node: xml.Node) !BitRange { + if (try node.get_attribute_int(u8, "lsb")) |lsb| { + if (try node.get_attribute_int(u8, "msb")) |msb| { + return .lsb_msb(lsb, msb); + } + } + if (try node.get_attribute_int(u8, "bitOffset")) |offset| { + if (try node.get_attribute_int(u8, "bitWidth")) |width| { + return .{ + .offset = offset, + .width = width, + }; + } + } + if (node.get_value("bitRange")) |bit_range_str| { + var it = std.mem.tokenizeAny(u8, bit_range_str, "[:]"); + const msb = try std.fmt.parseInt(u8, it.next() orelse return error.NoMsb, 0); + const lsb = try std.fmt.parseInt(u8, it.next() orelse return error.NoLsb, 0); + + return .lsb_msb(lsb, msb); + } + if (try node.get_attribute_int(u8, "width")) |width_bits| { + if (try node.get_attribute_int(u8, "end")) |end_bit| { + return .{ + .offset = end_bit, + .width = width_bits, + }; + } + } + return error.InvalidRange; +} diff --git a/tools/regz/src/svd.zig b/tools/regz/src/svd.zig index d4f9a5f05..1ec236921 100644 --- a/tools/regz/src/svd.zig +++ b/tools/regz/src/svd.zig @@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator; const xml = @import("xml"); const Arch = @import("arch.zig").Arch; +const BitRange = @import("BitRange.zig"); const Database = @import("Database.zig"); const Access = Database.Access; const StructID = Database.StructID; @@ -467,7 +468,7 @@ fn load_single_register(ctx: *Context, node: xml.Node, parent: StructID) !void { fn load_field(ctx: *Context, node: xml.Node, register_id: RegisterID, props: *const RegisterProperties) !void { const db = ctx.db; - const bit_range = try BitRange.parse(node); + const bit_range: BitRange = try .parse_xml(node); const dim_elements = try DimElements.parse(ctx, node); const count: ?u16 = if (dim_elements) |elements| count: { if (elements.dim_name != null) @@ -482,7 +483,7 @@ fn load_field(ctx: *Context, node: xml.Node, register_id: RegisterID, props: *co } const enum_id: ?EnumID = if (node.find_child(&.{"enumeratedValues"})) |enum_values_node| - try load_enumerated_values(ctx, enum_values_node, @intCast(bit_range.width)) + try load_enumerated_values(ctx, enum_values_node, bit_range.width) else null; @@ -508,8 +509,8 @@ fn load_field(ctx: *Context, node: xml.Node, register_id: RegisterID, props: *co }; } else try get_name_without_suffix(node, "%s"), .description = node.get_value("description"), - .size_bits = @intCast(bit_range.width), - .offset_bits = @intCast(bit_range.offset), + .size_bits = bit_range.width, + .offset_bits = bit_range.offset, .enum_id = enum_id, .access = access, }); @@ -833,58 +834,6 @@ const DimElements = struct { } }; -const BitRange = struct { - offset: u64, - width: u64, - - fn parse(node: xml.Node) !BitRange { - const lsb_opt = node.get_value("lsb"); - const msb_opt = node.get_value("msb"); - - if (lsb_opt != null and msb_opt != null) { - const lsb = try std.fmt.parseInt(u8, lsb_opt.?, 0); - const msb = try std.fmt.parseInt(u8, msb_opt.?, 0); - - if (msb < lsb) - return error.InvalidRange; - - return BitRange{ - .offset = lsb, - .width = msb - lsb + 1, - }; - } - - const bit_offset_opt = node.get_value("bitOffset"); - const bit_width_opt = node.get_value("bitWidth"); - if (bit_offset_opt != null and bit_width_opt != null) { - const offset = try std.fmt.parseInt(u8, bit_offset_opt.?, 0); - const width = try std.fmt.parseInt(u8, bit_width_opt.?, 0); - - return BitRange{ - .offset = offset, - .width = width, - }; - } - - const bit_range_opt = node.get_value("bitRange"); - if (bit_range_opt) |bit_range_str| { - var it = std.mem.tokenizeAny(u8, bit_range_str, "[:]"); - const msb = try std.fmt.parseInt(u8, it.next() orelse return error.NoMsb, 0); - const lsb = try std.fmt.parseInt(u8, it.next() orelse return error.NoLsb, 0); - - if (msb < lsb) - return error.InvalidRange; - - return BitRange{ - .offset = lsb, - .width = msb - lsb + 1, - }; - } - - return error.InvalidRange; - } -}; - // can only be one of these const Protection = enum { secure, diff --git a/tools/regz/src/targetdb.zig b/tools/regz/src/targetdb.zig index 0b2b202b7..7cf309c85 100644 --- a/tools/regz/src/targetdb.zig +++ b/tools/regz/src/targetdb.zig @@ -1,6 +1,7 @@ const std = @import("std"); const xml = @import("xml"); +const BitRange = @import("BitRange.zig"); const Database = @import("Database.zig"); const DeviceID = Database.DeviceID; const PeripheralID = Database.PeripheralID; @@ -236,8 +237,7 @@ fn load_bitfield(db: *Database, register_id: RegisterID, node: xml.Node) !void { const name = node.get_attribute("id") orelse return error.MissingField; const description = node.get_attribute("description"); - const width_bits = try node.get_attribute_int(u8, "width") orelse return error.MissingField; - const end_bit = try node.get_attribute_int(u8, "end") orelse return error.MissingField; + const bit_range: BitRange = try .parse_xml(node); const access_str = node.get_attribute("rwaccess"); const access: Database.Access = if (access_str) |str| @@ -258,7 +258,7 @@ fn load_bitfield(db: *Database, register_id: RegisterID, node: xml.Node) !void { const enum_id: ?EnumID = if (node.find_child(&.{"bitenum"}) != null) blk: { const enum_id = try db.create_enum(null, .{ - .size_bits = width_bits, + .size_bits = bit_range.width, }); var bitenum_it = node.iterate(&.{}, &.{"bitenum"}); @@ -278,7 +278,7 @@ fn load_bitfield(db: *Database, register_id: RegisterID, node: xml.Node) !void { .description = description, .enum_id = enum_id, .access = access, - .offset_bits = end_bit, - .size_bits = width_bits, + .offset_bits = bit_range.offset, + .size_bits = bit_range.width, }); } From 54493ba391d6dc2364d2b73b9afa919a94d76864 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Mon, 13 Jul 2026 14:39:15 +0200 Subject: [PATCH 08/17] regz: Unify register access parsing --- tools/regz/src/Database.zig | 39 +++++++++++++++++++++++++++++++++++++ tools/regz/src/atdf.zig | 13 +------------ tools/regz/src/svd.zig | 23 ++-------------------- tools/regz/src/targetdb.zig | 15 +++----------- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/tools/regz/src/Database.zig b/tools/regz/src/Database.zig index 0f6cd2d2a..9f0395faf 100644 --- a/tools/regz/src/Database.zig +++ b/tools/regz/src/Database.zig @@ -369,6 +369,45 @@ pub const Access = enum { break field_name; } else unreachable; } + + pub fn from_string(access: []const u8) ?Access { + // Convert to lowercase, to match zig enum style + var buf: [32]u8 = undefined; + // Matches nothing, used to avoid duplicating log statement + var lower: []u8 = ""; + + if (access.len <= buf.len) { + lower = std.ascii.lowerString(&buf, access); + std.mem.replaceScalar(u8, lower, '-', '_'); + std.mem.replaceScalar(u8, lower, '/', '_'); + } + + // Try to match against all enum values + return if (std.meta.stringToEnum(Access, lower)) |ret| + ret + else if (std.mem.eql(u8, "r", lower)) + .read_only + else if (std.mem.eql(u8, "w", lower)) + .write_only + else if (std.mem.eql(u8, "rw", lower)) + .read_write + else if (std.mem.eql(u8, "r_w", lower)) + .read_write + else if (std.mem.eql(u8, "read", lower)) + .read_only + else if (std.mem.eql(u8, "write", lower)) + .write_only + else if (std.mem.eql(u8, "readwrite", lower)) + .read_write + else if (std.mem.eql(u8, "writeonce", lower)) + .write_once + else if (std.mem.eql(u8, "read_writeonce", lower)) + .read_write_once + else { + log.warn("invalid access type: '{s}'", .{access}); + return null; + }; + } }; pub const StructLayout = enum { diff --git a/tools/regz/src/atdf.zig b/tools/regz/src/atdf.zig index d57d4561d..412f488a2 100644 --- a/tools/regz/src/atdf.zig +++ b/tools/regz/src/atdf.zig @@ -543,7 +543,7 @@ fn load_register( .count = try node.get_attribute_int(u64, "count"), .reset_value = try node.get_attribute_int(u64, "initval"), .access = if (node.get_attribute("rw")) |access_str| - try access_from_string(access_str) + Database.Access.from_string(access_str) orelse return error.InvalidAccessStr else .read_write, }); @@ -701,17 +701,6 @@ fn load_field(ctx: *Context, node: xml.Node, peripheral_struct_id: StructID, par } } -fn access_from_string(str: []const u8) !Database.Access { - return if (std.mem.eql(u8, "RW", str)) - .read_write - else if (std.mem.eql(u8, "R", str)) - .read_only - else if (std.mem.eql(u8, "W", str)) - .write_only - else - error.InvalidAccessStr; -} - fn load_enum( ctx: *Context, module_node: xml.Node, diff --git a/tools/regz/src/svd.zig b/tools/regz/src/svd.zig index 1ec236921..b4e56d629 100644 --- a/tools/regz/src/svd.zig +++ b/tools/regz/src/svd.zig @@ -488,7 +488,7 @@ fn load_field(ctx: *Context, node: xml.Node, register_id: RegisterID, props: *co null; const access = if (node.get_value("access")) |access_str| - parse_access(access_str) catch blk: { + Access.from_string(access_str) orelse blk: { log.warn("Failed to parse access string '{s}', it must be one of 'read-value', 'write-only', 'read-write', 'writeOnce', or 'read-writeOnce', defaulting to 'read-write'", .{access_str}); break :blk .read_write; } @@ -860,7 +860,7 @@ const RegisterProperties = struct { else null, .access = if (node.get_value("access")) |access_str| - parse_access(access_str) catch blk: { + Access.from_string(access_str) orelse blk: { log.warn("Failed to parse access string '{s}', it must be one of 'read-only'," ++ "'write-only', 'write', 'read-write', 'writeOnce', or 'read-writeOnce', " ++ "defaulting to 'read-write'", .{access_str}); @@ -881,25 +881,6 @@ const RegisterProperties = struct { } }; -fn parse_access(str: []const u8) !Access { - return if (std.ascii.eqlIgnoreCase("read-only", str)) - Access.read_only - else if (std.ascii.eqlIgnoreCase("write-only", str)) - Access.write_only - else if (std.ascii.eqlIgnoreCase("write", str)) - Access.write_only - else if (std.ascii.eqlIgnoreCase("read-write", str)) - Access.read_write - else if (std.ascii.eqlIgnoreCase("writeOnce", str)) - Access.write_once - else if (std.ascii.eqlIgnoreCase("read-writeOnce", str)) - Access.read_write_once - else blk: { - log.warn("invalid access type: '{s}'", .{str}); - break :blk error.UnknownAccessType; - }; -} - test "svd.device register properties" { const text = \\ diff --git a/tools/regz/src/targetdb.zig b/tools/regz/src/targetdb.zig index 7cf309c85..836b42b02 100644 --- a/tools/regz/src/targetdb.zig +++ b/tools/regz/src/targetdb.zig @@ -239,18 +239,9 @@ fn load_bitfield(db: *Database, register_id: RegisterID, node: xml.Node) !void { const bit_range: BitRange = try .parse_xml(node); - const access_str = node.get_attribute("rwaccess"); - const access: Database.Access = if (access_str) |str| - if (std.mem.eql(u8, str, "RW")) - .read_write - else if (std.mem.eql(u8, str, "R/W")) - .read_write - else if (std.mem.eql(u8, str, "R")) - .read_only - else if (std.mem.eql(u8, str, "W")) - .write_only - else blk: { - log.info("unrecognized access string: '{s}'", .{str}); + const access = if (node.get_attribute("rwaccess")) |access_str| + Database.Access.from_string(access_str) orelse blk: { + log.info("unrecognized access string: '{s}'", .{access_str}); break :blk .read_write; } else From 7374fbf54705a23ed39ea4f0b31fece157633ce7 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Mon, 13 Jul 2026 19:07:06 +0200 Subject: [PATCH 09/17] mspm0: Uart support --- examples/texasinstruments/mspm0/build.zig | 31 ++--- .../texasinstruments/mspm0/src/uart_echo.zig | 40 ++++++ port/texasinstruments/mspm0/build.zig | 1 + port/texasinstruments/mspm0/patches/gpio.zon | 36 +++-- port/texasinstruments/mspm0/patches/uart.zon | 22 +++ port/texasinstruments/mspm0/src/hal.zig | 1 + port/texasinstruments/mspm0/src/hal/Uart.zig | 125 ++++++++++++++++++ port/texasinstruments/mspm0/src/hal/gpio.zig | 3 + 8 files changed, 223 insertions(+), 36 deletions(-) create mode 100644 examples/texasinstruments/mspm0/src/uart_echo.zig create mode 100644 port/texasinstruments/mspm0/patches/uart.zon create mode 100644 port/texasinstruments/mspm0/src/hal/Uart.zig diff --git a/examples/texasinstruments/mspm0/build.zig b/examples/texasinstruments/mspm0/build.zig index e21ae8663..792a18b3f 100644 --- a/examples/texasinstruments/mspm0/build.zig +++ b/examples/texasinstruments/mspm0/build.zig @@ -12,22 +12,19 @@ pub fn build(b: *std.Build) void { inline for (comptime std.meta.fieldNames(@TypeOf(mb.ports.mspm0.chips))) |field_name| { const target = @field(mb.ports.mspm0.chips, field_name); - const raw_blinky = mb.add_firmware(.{ - .name = b.fmt("raw_blinky_{s}", .{field_name}), - .target = target, - .optimize = optimize, - .root_source_file = b.path("src/raw_blinky.zig"), - }); - - mb.install_firmware(raw_blinky, .{}); - - const blinky = mb.add_firmware(.{ - .name = b.fmt("blinky_{s}", .{field_name}), - .target = target, - .optimize = optimize, - .root_source_file = b.path("src/blinky.zig"), - }); - - mb.install_firmware(blinky, .{}); + inline for ([_][]const u8{ + "blinky", + "raw_blinky", + "uart_echo", + }) |name| { + const example = mb.add_firmware(.{ + .name = name ++ "_" ++ field_name, + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/" ++ name ++ ".zig"), + }); + + mb.install_firmware(example, .{}); + } } } diff --git a/examples/texasinstruments/mspm0/src/uart_echo.zig b/examples/texasinstruments/mspm0/src/uart_echo.zig new file mode 100644 index 000000000..b37580e29 --- /dev/null +++ b/examples/texasinstruments/mspm0/src/uart_echo.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const mspm0 = microzig.hal; + +pub const panic = microzig.panic; +pub const std_options = microzig.std_options(.{}); +comptime { + _ = microzig.export_startup(); +} + +pub fn main() void { + mspm0.gpio.enable(.gpioa); + + const led = mspm0.gpio.num(.gpioa, 22); + const uart_rx = mspm0.gpio.num(.gpioa, 26); + const uart_tx = mspm0.gpio.num(.gpioa, 27); + + led.set_function(1); + led.set_direction(.out); + + uart_rx.set_function(3); + uart_tx.set_function(5); + + const uart = mspm0.Uart.num(0); + uart.configure(.{ + .clk = .{ .div = .from_ratio(3) }, + }); + + while (true) { + led.toggle(); + + if (uart.read()) |c| + _ = uart.write(std.ascii.toUpper(c)) + else + _ = uart.write('.'); + + for (0..1_000_000) |_| + asm volatile ("nop"); + } +} diff --git a/port/texasinstruments/mspm0/build.zig b/port/texasinstruments/mspm0/build.zig index f1e12c576..fd483bf4a 100644 --- a/port/texasinstruments/mspm0/build.zig +++ b/port/texasinstruments/mspm0/build.zig @@ -96,6 +96,7 @@ pub fn init(dep: *std.Build.Dependency) ?@This() { const patch_paths = [_][]const u8{ "patches/gpio.zon", + "patches/uart.zon", }; const patch_files = b.allocator.alloc(std.Build.LazyPath, patch_paths.len) catch @panic("OOM"); for (patch_files, patch_paths) |*dst, src| diff --git a/port/texasinstruments/mspm0/patches/gpio.zon b/port/texasinstruments/mspm0/patches/gpio.zon index c3b9aec62..60e5a79f0 100644 --- a/port/texasinstruments/mspm0/patches/gpio.zon +++ b/port/texasinstruments/mspm0/patches/gpio.zon @@ -1,23 +1,21 @@ .{ - .{ - .add_enum_and_apply = .{ - .parent = "types.peripherals.gpioa", - .@"enum" = .{ - .name = "ChannelID", - .bitsize = 2, - .fields = .{ - .{ .name = "none", .value = 0 }, - .{ .name = "ch_1", .value = 1 }, - .{ .name = "ch_2", .value = 2 }, - .{ .name = "ch_3", .value = 3 }, - }, - }, - .apply_to = .{ - "types.peripherals.gpioa.GPIOA_FSUB_0.CHANID", - "types.peripherals.gpioa.GPIOA_FSUB_1.CHANID", - "types.peripherals.gpioa.GPIOA_FPUB_0.CHANID", - "types.peripherals.gpioa.GPIOA_FPUB_1.CHANID", + .{ .add_enum_and_apply = .{ + .parent = "types.peripherals.gpioa", + .@"enum" = .{ + .name = "ChannelID", + .bitsize = 2, + .fields = .{ + .{ .name = "none", .value = 0 }, + .{ .name = "ch_1", .value = 1 }, + .{ .name = "ch_2", .value = 2 }, + .{ .name = "ch_3", .value = 3 }, }, }, - }, + .apply_to = .{ + "types.peripherals.gpioa.GPIOA_FSUB_0.CHANID", + "types.peripherals.gpioa.GPIOA_FSUB_1.CHANID", + "types.peripherals.gpioa.GPIOA_FPUB_0.CHANID", + "types.peripherals.gpioa.GPIOA_FPUB_1.CHANID", + }, + } }, } diff --git a/port/texasinstruments/mspm0/patches/uart.zon b/port/texasinstruments/mspm0/patches/uart.zon new file mode 100644 index 000000000..617153d51 --- /dev/null +++ b/port/texasinstruments/mspm0/patches/uart.zon @@ -0,0 +1,22 @@ +.{ + .{ .add_enum_and_apply = .{ + .parent = "types.peripherals.uart0", + .@"enum" = .{ + .name = "ClkdivRatio", + .bitsize = 3, + .fields = .{ + .{ .name = "1", .value = 0 }, + .{ .name = "2", .value = 1 }, + .{ .name = "3", .value = 2 }, + .{ .name = "4", .value = 3 }, + .{ .name = "5", .value = 4 }, + .{ .name = "6", .value = 5 }, + .{ .name = "7", .value = 6 }, + .{ .name = "8", .value = 7 }, + }, + }, + .apply_to = .{ + "types.peripherals.uart0.UART0_CLKDIV.RATIO", + }, + } }, +} diff --git a/port/texasinstruments/mspm0/src/hal.zig b/port/texasinstruments/mspm0/src/hal.zig index 6b50631c2..c45d1fdba 100644 --- a/port/texasinstruments/mspm0/src/hal.zig +++ b/port/texasinstruments/mspm0/src/hal.zig @@ -1 +1,2 @@ pub const gpio = @import("hal/gpio.zig"); +pub const Uart = @import("hal/Uart.zig"); diff --git a/port/texasinstruments/mspm0/src/hal/Uart.zig b/port/texasinstruments/mspm0/src/hal/Uart.zig new file mode 100644 index 000000000..808077479 --- /dev/null +++ b/port/texasinstruments/mspm0/src/hal/Uart.zig @@ -0,0 +1,125 @@ +const std = @import("std"); +const microzig = @import("microzig"); + +const peri_types = microzig.chip.types.peripherals; +const Uart = @This(); + +regs: *volatile peri_types.uart0, + +pub fn num(n: comptime_int) Uart { + const name = std.fmt.comptimePrint("uart{}", .{n}); + return .{ .regs = if (@hasDecl(microzig.chip.peripherals, name)) + @field(microzig.chip.peripherals, name) + else + @compileError(name ++ " not present or not supported") }; +} + +pub const Config = struct { + pub const Clock = struct { + pub const Source = enum { BUSCLK, MFCLK, LFCLK }; + + src: Source = .BUSCLK, + div0: peri_types.uart0.ClkdivRatio = .@"1", + ovs: RegFieldType("UART0_CTL0", "HSE") = .OVS16, + div: IntFracDiv(16, 6), + }; + + clk: Clock, + bits: RegFieldType("UART0_LCRH", "WLEN") = .DATABIT8, + loopback: bool = false, + manchester_encode: bool = false, + enable: bool = true, +}; + +/// See Technical Reference Manual 14.2.6: Initialization +pub fn configure(self: Uart, cfg: Config) void { + self.regs.UART0_PWREN.raw = 0x2600_0001; + // Technical reference manual part 2.2.6 + inline for (0..4) |_| + asm volatile ("nop"); + self.regs.UART0_CLKSEL.write(.{ + .LFCLK_SEL = @enumFromInt(@intFromBool(cfg.clk.src == .LFCLK)), + .MFCLK_SEL = @enumFromInt(@intFromBool(cfg.clk.src == .MFCLK)), + .BUSCLK_SEL = @enumFromInt(@intFromBool(cfg.clk.src == .BUSCLK)), + }); + self.regs.UART0_CLKDIV.write(.{ .RATIO = cfg.clk.div0 }); + var ctl0 = self.regs.UART0_CTL0.read(); + if (ctl0.ENABLE != .DISABLE) { + ctl0.ENABLE = .DISABLE; + self.regs.UART0_CTL0.write(ctl0); + } + self.regs.UART0_IBRD.write(.{ .DIVINT = @enumFromInt(cfg.clk.div.int) }); + self.regs.UART0_FBRD.write(.{ .DIVFRAC = @enumFromInt(cfg.clk.div.frac) }); + + // There are more ctl0 options + ctl0.LBE = if (cfg.loopback) .ENABLE else .DISABLE; + ctl0.MENC = if (cfg.manchester_encode) .ENABLE else .DISABLE; + ctl0.HSE = cfg.clk.ovs; + self.regs.UART0_CTL0.write(ctl0); + + self.regs.UART0_LCRH.write(.{ + .BRK = .DISABLE, + .PEN = .DISABLE, + .EPS = .ODD, + .STP2 = .DISABLE, + .WLEN = cfg.bits, + .SPS = .DISABLE, + .SENDIDLE = .DISABLE, + .EXTDIR_SETUP = @enumFromInt(0), + .EXTDIR_HOLD = @enumFromInt(0), + }); + if (cfg.enable) { + ctl0.ENABLE = .ENABLE; + self.regs.UART0_CTL0.write(ctl0); + } +} + +pub fn disable(self: Uart) void { + self.regs.PWREN = 0; +} + +pub fn write(self: Uart, b: u8) bool { + const stat = self.regs.UART0_STAT.read(); + if (stat.TXFF == .SET) return false; + self.regs.UART0_TXDATA.write(.{ .DATA = @enumFromInt(b) }); + return true; +} + +pub fn read(self: Uart) ?u8 { + const stat = self.regs.UART0_STAT.read(); + if (stat.RXFE == .SET) return null; + return @intFromEnum(self.regs.UART0_TXDATA.read().DATA); +} + +fn RegFieldType(register_name: []const u8, field_name: []const u8) type { + const reg_idx = std.meta.fieldIndex(peri_types.uart0, register_name) orelse + @compileError("No register " ++ register_name ++ " in uart0."); + const Reg = std.meta.fieldTypes(peri_types.uart0)[reg_idx].underlying_type; + + const fld_idx = std.meta.fieldIndex(Reg, field_name) orelse + @compileError("No field " ++ field_name ++ " in uart0." ++ register_name ++ "."); + return std.meta.fieldTypes(Reg)[fld_idx]; +} + +// Maybe move this into core? +pub fn IntFracDiv(int_bits: comptime_int, frac_bits: comptime_int) type { + return struct { + pub const Int = @Int(.unsigned, int_bits); + pub const Frac = @Int(.unsigned, frac_bits); + + int: Int, + frac: Frac = 0, + + // Returns clock configuration that most closely matches the given ratio + pub fn from_ratio(ratio: comptime_float) @This() { + const int = @floor(ratio); + if (int >= (1 << int_bits)) + @compileError("Divider too big"); + + return .{ + .int = @intFromFloat(int), + .frac = (ratio - int) * (1 << frac_bits), + }; + } + }; +} diff --git a/port/texasinstruments/mspm0/src/hal/gpio.zig b/port/texasinstruments/mspm0/src/hal/gpio.zig index 33b005c18..fa9aa6074 100644 --- a/port/texasinstruments/mspm0/src/hal/gpio.zig +++ b/port/texasinstruments/mspm0/src/hal/gpio.zig @@ -13,6 +13,9 @@ fn instance(port: Port) *volatile microzig.chip.types.peripherals.gpioa { pub fn enable(port: Port) void { instance(port).GPIOA_PWREN.raw = 0x26000001; + // Technical reference manual part 2.2.6 + inline for (0..4) |_| + asm volatile ("nop"); } pub const Direction = enum(u1) { in, out }; From 6e87b551586c5193b3aa8c351d184a8d9141f2d7 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Mon, 13 Jul 2026 22:15:29 +0200 Subject: [PATCH 10/17] mspm0: UART writer and logger --- examples/texasinstruments/mspm0/build.zig | 1 + .../texasinstruments/mspm0/src/uart_log.zig | 45 ++++++++++ port/texasinstruments/mspm0/src/hal/Uart.zig | 82 +++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 examples/texasinstruments/mspm0/src/uart_log.zig diff --git a/examples/texasinstruments/mspm0/build.zig b/examples/texasinstruments/mspm0/build.zig index 792a18b3f..d85880cc6 100644 --- a/examples/texasinstruments/mspm0/build.zig +++ b/examples/texasinstruments/mspm0/build.zig @@ -16,6 +16,7 @@ pub fn build(b: *std.Build) void { "blinky", "raw_blinky", "uart_echo", + "uart_log", }) |name| { const example = mb.add_firmware(.{ .name = name ++ "_" ++ field_name, diff --git a/examples/texasinstruments/mspm0/src/uart_log.zig b/examples/texasinstruments/mspm0/src/uart_log.zig new file mode 100644 index 000000000..2b5a39600 --- /dev/null +++ b/examples/texasinstruments/mspm0/src/uart_log.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const mspm0 = microzig.hal; + +pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { + std.log.err("panic: {s}", .{message}); + while (true) @breakpoint(); +} + +pub const std_options = microzig.std_options(.{ + .log_level = .debug, + .logFn = mspm0.Uart.log, +}); + +comptime { + _ = microzig.export_startup(); +} + +pub fn main() void { + mspm0.gpio.enable(.gpioa); + + { // init logger + const uart_tx = mspm0.gpio.num(.gpioa, 27); + uart_tx.set_function(5); + const uart = mspm0.Uart.num(0); + uart.configure(.{ + .clk = .{ .div = .from_ratio(3) }, + }); + mspm0.Uart.init_logger(uart); + } + + const led = mspm0.gpio.num(.gpioa, 22); + + led.set_function(1); + led.set_direction(.out); + + var i: u32 = 0; + while (true) : (i +%= 1) { + std.log.info("Toggling LED {}", .{i}); + led.toggle(); + + for (0..2_000_000) |_| + asm volatile ("nop"); + } +} diff --git a/port/texasinstruments/mspm0/src/hal/Uart.zig b/port/texasinstruments/mspm0/src/hal/Uart.zig index 808077479..6c075a0ee 100644 --- a/port/texasinstruments/mspm0/src/hal/Uart.zig +++ b/port/texasinstruments/mspm0/src/hal/Uart.zig @@ -91,6 +91,88 @@ pub fn read(self: Uart) ?u8 { return @intFromEnum(self.regs.UART0_TXDATA.read().DATA); } +pub const Writer = struct { + const vtable: *const std.Io.Writer.VTable = &.{ + .drain = drain, + }; + + instance: Uart, + seek: usize = 0, + interface: std.Io.Writer, + + pub fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { + _ = splat; + const self: *Writer = @fieldParentPtr("interface", w); + + // Drain interface buffer first + while (self.seek != w.end) { + if (!self.instance.write(w.buffer[self.seek])) + return 0; + self.seek += 1; + } + w.end = 0; + self.seek = 0; + + // Only drain from the first data slice + for (data[0], 0..) |c, i| { + if (!self.instance.write(c)) + return i; + } + return data[0].len; + } +}; + +pub fn writer(self: Uart, buffer: []u8) Writer { + return .{ + .instance = self, + .interface = .{ + .vtable = Writer.vtable, + .buffer = buffer, + }, + }; +} + +var logger: ?Uart.Writer = null; + +/// Set a specific uart instance to be used for logging. +/// +/// Allows system logging over uart via: +/// pub const microzig_options = .{ +/// .logFn = hal.uart.log, +/// }; +pub fn init_logger(uart: Uart) void { + logger = uart.writer(""); + logger.?.interface.writeAll("\r\n================ STARTING NEW LOGGER ================\r\n") catch {}; +} + +/// Disables logging via the uart instance. +pub fn deinit_logger() void { + logger = null; +} + +pub fn log( + comptime level: std.log.Level, + comptime scope: @TypeOf(.EnumLiteral), + comptime format: []const u8, + args: anytype, +) void { + // const level_prefix = comptime "[{}.{:0>6}] " ++ level.asText(); + const prefix = comptime level.asText() ++ switch (scope) { + .default => ": ", + else => " (" ++ @tagName(scope) ++ "): ", + }; + + if (logger) |*w| { + // const current_time = time.get_time_since_boot(); + // const seconds = current_time.to_us() / std.time.us_per_s; + // const microseconds = current_time.to_us() % std.time.us_per_s; + + // w.interface.print(prefix ++ format ++ "\r\n", .{ seconds, microseconds } ++ args) catch {}; + w.interface.print(prefix ++ format ++ "\r\n", args) catch {}; + w.interface.flush() catch {}; + } +} + fn RegFieldType(register_name: []const u8, field_name: []const u8) type { const reg_idx = std.meta.fieldIndex(peri_types.uart0, register_name) orelse @compileError("No register " ++ register_name ++ " in uart0."); From e2cf44ef3082747c477f464836d95850b7813826 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Mon, 13 Jul 2026 22:46:49 +0200 Subject: [PATCH 11/17] fix CI failures --- .github/workflows/ports.yml | 4 ++++ port/stmicro/stm32/src/generate.zig | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ports.yml b/.github/workflows/ports.yml index 5aeacf5e0..dce20eace 100644 --- a/.github/workflows/ports.yml +++ b/.github/workflows/ports.yml @@ -63,6 +63,10 @@ jobs: run: zig build test working-directory: port/texasinstruments/msp430 + - name: texasinstruments/mspm0 + run: zig build test + working-directory: port/texasinstruments/mspm0 + - name: texasinstruments/tm4c run: zig build test working-directory: port/texasinstruments/tm4c diff --git a/port/stmicro/stm32/src/generate.zig b/port/stmicro/stm32/src/generate.zig index 5ba27bccc..c538c72f9 100644 --- a/port/stmicro/stm32/src/generate.zig +++ b/port/stmicro/stm32/src/generate.zig @@ -140,7 +140,7 @@ fn generate_chips_file( , .{ std.zig.fmtId(chip_file.name), std.zig.fmtId(chip_file.name), - regz.embassy.core_to_cpu.get(core.name).?, + regz.Arch.from_string(core.name), }); if (with_fpu) { From b0e3e612e1a0f03031163bb0fd33280c5435a6ae Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Mon, 13 Jul 2026 22:55:54 +0200 Subject: [PATCH 12/17] fix CI failures 2 --- .github/workflows/ports.yml | 4 ++++ port/stmicro/stm32/src/generate.zig | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ports.yml b/.github/workflows/ports.yml index dce20eace..53e5e2e29 100644 --- a/.github/workflows/ports.yml +++ b/.github/workflows/ports.yml @@ -133,6 +133,10 @@ jobs: run: zig build -Doptimize=ReleaseSmall working-directory: examples/texasinstruments/msp430 + - name: texasinstruments/mspm0 + run: zig build -Doptimize=ReleaseSmall + working-directory: examples/texasinstruments/mspm0 + - name: texasinstruments/tm4c run: zig build -Doptimize=ReleaseSmall working-directory: examples/texasinstruments/tm4c diff --git a/port/stmicro/stm32/src/generate.zig b/port/stmicro/stm32/src/generate.zig index c538c72f9..c2d40699f 100644 --- a/port/stmicro/stm32/src/generate.zig +++ b/port/stmicro/stm32/src/generate.zig @@ -134,7 +134,7 @@ fn generate_chips_file( \\ .preferred_binary_format = .elf, \\ .zig_target = .{{ \\ .cpu_arch = .thumb, - \\ .cpu_model = .{{ .explicit = &std.Target.arm.cpu.{s} }}, + \\ .cpu_model = .{{ .explicit = &std.Target.arm.cpu.{t} }}, \\ .os_tag = .freestanding, \\ , .{ From 919d0e4801c9240760604cfc99b4b32336fff6f9 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Mon, 13 Jul 2026 23:20:49 +0200 Subject: [PATCH 13/17] regz: add get_value_int to xml nodes --- tools/regz/src/BitRange.zig | 8 ++++---- tools/regz/src/svd.zig | 40 ++++++++++--------------------------- tools/regz/src/xml.zig | 6 ++++++ 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/tools/regz/src/BitRange.zig b/tools/regz/src/BitRange.zig index 1c25c001f..e8dbcf13c 100644 --- a/tools/regz/src/BitRange.zig +++ b/tools/regz/src/BitRange.zig @@ -17,13 +17,13 @@ pub fn lsb_msb(lsb: u8, msb: u8) !BitRange { } pub fn parse_xml(node: xml.Node) !BitRange { - if (try node.get_attribute_int(u8, "lsb")) |lsb| { - if (try node.get_attribute_int(u8, "msb")) |msb| { + if (try node.get_value_int(u8, "lsb")) |lsb| { + if (try node.get_value_int(u8, "msb")) |msb| { return .lsb_msb(lsb, msb); } } - if (try node.get_attribute_int(u8, "bitOffset")) |offset| { - if (try node.get_attribute_int(u8, "bitWidth")) |width| { + if (try node.get_value_int(u8, "bitOffset")) |offset| { + if (try node.get_value_int(u8, "bitWidth")) |width| { return .{ .offset = offset, .width = width, diff --git a/tools/regz/src/svd.zig b/tools/regz/src/svd.zig index b4e56d629..70fbe7ab1 100644 --- a/tools/regz/src/svd.zig +++ b/tools/regz/src/svd.zig @@ -197,9 +197,7 @@ fn derive_peripherals(ctx: *Context, device_id: DeviceID) !void { .description = node.get_value("description"), .struct_id = struct_id, .count = count, - .offset_bytes = if (node.get_value("baseAddress")) |base_address| - try std.fmt.parseInt(u64, base_address, 0) - else + .offset_bytes = try node.get_value_int(u64, "baseAddress") orelse return error.PeripheralMissingBaseAddress, }); } @@ -248,9 +246,7 @@ pub fn load_peripheral(ctx: *Context, node: xml.Node, device_id: DeviceID) !void .description = node.get_value("description"), .struct_id = struct_id, .count = count, - .offset_bytes = if (node.get_value("baseAddress")) |base_address| - try std.fmt.parseInt(u64, base_address, 0) - else + .offset_bytes = try node.get_value_int(u64, "baseAddress") orelse return error.PeripheralMissingBaseAddress, }); _ = instance_id; @@ -310,7 +306,8 @@ fn load_cluster( // Note that dimable identifier type means that it can include a %s in the name, it's a copy of a previous identifier const name = node.get_value("name") orelse return error.MissingClusterName; const description = node.get_value("description"); - const address_offset_str = node.get_value("addressOffset") orelse return error.MissingClusterOffset; + const address_offset = try node.get_value_int(u64, "addressOffset") orelse + return error.MissingClusterOffset; const alternate_cluster = node.get_value("alternateCluster"); if (alternate_cluster != null) return error.TodoAlternateCluster; @@ -325,7 +322,7 @@ fn load_cluster( .name = name[0 .. name.len - "[%s]".len], .struct_id = struct_id, .description = description, - .offset_bytes = try std.fmt.parseInt(u32, address_offset_str, 0), + .offset_bytes = address_offset, .count = array.count, .size_bytes = array.increment, }), @@ -346,7 +343,7 @@ fn load_cluster( .name = name, .struct_id = struct_id, .description = description, - .offset_bytes = try std.fmt.parseInt(u32, address_offset_str, 0), + .offset_bytes = address_offset, }); } @@ -743,14 +740,8 @@ const DimElements = struct { } fn parse(ctx: *Context, node: xml.Node) !?DimElements { - const dim_increment = if (node.get_value("dimIncrement")) |dim_increment_str| - try std.fmt.parseInt(u64, dim_increment_str, 0) - else - null; - const dim = if (node.get_value("dim")) |dim_str| - try std.fmt.parseInt(u64, dim_str, 0) - else - null; + const dim_increment = try node.get_value_int(u64, "dimIncrement"); + const dim = try node.get_value_int(u64, "dim"); var dim_index = node.get_value("dimIndex"); // if "dimIncrement" exists, "dim" becomes a mandatory field @@ -855,10 +846,7 @@ const RegisterProperties = struct { fn parse(node: xml.Node) !RegisterProperties { return RegisterProperties{ - .size = if (node.get_value("size")) |size_str| - try std.fmt.parseInt(u64, size_str, 0) - else - null, + .size = try node.get_value_int(u64, "size"), .access = if (node.get_value("access")) |access_str| Access.from_string(access_str) orelse blk: { log.warn("Failed to parse access string '{s}', it must be one of 'read-only'," ++ @@ -869,14 +857,8 @@ const RegisterProperties = struct { else null, .protection = null, - .reset_value = if (node.get_value("resetValue")) |size_str| - try std.fmt.parseInt(u64, size_str, 0) - else - null, - .reset_mask = if (node.get_value("resetMask")) |size_str| - try std.fmt.parseInt(u64, size_str, 0) - else - null, + .reset_value = try node.get_value_int(u64, "resetValue"), + .reset_mask = try node.get_value_int(u64, "resetMask"), }; } }; diff --git a/tools/regz/src/xml.zig b/tools/regz/src/xml.zig index a1dbdf175..994592eef 100644 --- a/tools/regz/src/xml.zig +++ b/tools/regz/src/xml.zig @@ -127,6 +127,12 @@ pub const Node = struct { else null; } + + pub fn get_value_int(node: Node, T: type, key: [:0]const u8) !?T { + const buf = get_value(node, key) orelse return null; + const buf_trim = std.mem.trim(u8, buf, &std.ascii.whitespace); + return std.fmt.parseInt(T, buf_trim, 0) catch |e| e; + } }; pub const Doc = struct { From 67fb3d301d3af59a37031ab411fae186c41ad5d9 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Mon, 13 Jul 2026 23:23:58 +0200 Subject: [PATCH 14/17] regz: dumb fix --- tools/regz/src/svd.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/regz/src/svd.zig b/tools/regz/src/svd.zig index 70fbe7ab1..cf1a7cc70 100644 --- a/tools/regz/src/svd.zig +++ b/tools/regz/src/svd.zig @@ -358,7 +358,7 @@ fn load_cluster( while (cluster_it.next()) |cluster_node| try load_cluster(ctx, cluster_node, struct_id); - log.debug("loaded cluster name: {s} description={?s} offset={s}", .{ name, description, address_offset_str }); + log.debug("loaded cluster name: {s} description={?s} offset=0x{X}", .{ name, description, address_offset }); } fn get_name_without_suffix(node: xml.Node, suffix: []const u8) ![]const u8 { From f4e6776f61168473b79714da22a23fadeecf0b3d Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 14 Jul 2026 02:16:55 +0200 Subject: [PATCH 15/17] core: add utility struct for fractional clock dividers --- core/src/utilities.zig | 22 +++++++++++++++++ port/texasinstruments/mspm0/src/hal/Uart.zig | 25 +------------------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/core/src/utilities.zig b/core/src/utilities.zig index 3614d9eeb..93c937529 100644 --- a/core/src/utilities.zig +++ b/core/src/utilities.zig @@ -651,3 +651,25 @@ test "CircularBuffer bounds" { try std.testing.expectError(error.Full, maybe_err); } + +pub fn IntFracDiv(int_bits: comptime_int, frac_bits: comptime_int) type { + return struct { + pub const Int = @Int(.unsigned, int_bits); + pub const Frac = @Int(.unsigned, frac_bits); + + int: Int, + frac: Frac = 0, + + // Returns clock configuration that most closely matches the given ratio + pub fn from_ratio(ratio: comptime_float) @This() { + const int = @floor(ratio); + if (int >= (1 << int_bits)) + @compileError("Divider too big"); + + return .{ + .int = @intFromFloat(int), + .frac = (ratio - int) * (1 << frac_bits), + }; + } + }; +} diff --git a/port/texasinstruments/mspm0/src/hal/Uart.zig b/port/texasinstruments/mspm0/src/hal/Uart.zig index 6c075a0ee..e62a8fa43 100644 --- a/port/texasinstruments/mspm0/src/hal/Uart.zig +++ b/port/texasinstruments/mspm0/src/hal/Uart.zig @@ -21,7 +21,7 @@ pub const Config = struct { src: Source = .BUSCLK, div0: peri_types.uart0.ClkdivRatio = .@"1", ovs: RegFieldType("UART0_CTL0", "HSE") = .OVS16, - div: IntFracDiv(16, 6), + div: microzig.utilities.IntFracDiv(16, 6), }; clk: Clock, @@ -182,26 +182,3 @@ fn RegFieldType(register_name: []const u8, field_name: []const u8) type { @compileError("No field " ++ field_name ++ " in uart0." ++ register_name ++ "."); return std.meta.fieldTypes(Reg)[fld_idx]; } - -// Maybe move this into core? -pub fn IntFracDiv(int_bits: comptime_int, frac_bits: comptime_int) type { - return struct { - pub const Int = @Int(.unsigned, int_bits); - pub const Frac = @Int(.unsigned, frac_bits); - - int: Int, - frac: Frac = 0, - - // Returns clock configuration that most closely matches the given ratio - pub fn from_ratio(ratio: comptime_float) @This() { - const int = @floor(ratio); - if (int >= (1 << int_bits)) - @compileError("Divider too big"); - - return .{ - .int = @intFromFloat(int), - .frac = (ratio - int) * (1 << frac_bits), - }; - } - }; -} From 92eae1c24858cb15f06efd02bbb390b03378b16d Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 14 Jul 2026 03:42:08 +0200 Subject: [PATCH 16/17] use fractional clock divider across the project --- core/src/utilities.zig | 45 +++++++++++++++---- .../rp2xxx/src/rp2040_only/tiles.zig | 2 +- .../raspberrypi/rp2xxx/src/squarewave.zig | 2 +- .../raspberrypi/rp2xxx/src/st7789_lcd.zig | 2 +- examples/raspberrypi/rp2xxx/src/ws2812.zig | 2 +- .../texasinstruments/mspm0/src/uart_echo.zig | 2 +- .../texasinstruments/mspm0/src/uart_log.zig | 2 +- port/raspberrypi/rp2xxx/src/hal/adc.zig | 1 + .../rp2xxx/src/hal/cyw43439_pio_spi.zig | 8 ++-- .../rp2xxx/src/hal/cyw43_pio_spi.zig | 2 +- .../raspberrypi/rp2xxx/src/hal/pio/common.zig | 18 ++------ port/raspberrypi/rp2xxx/src/hal/pwm.zig | 17 ++++--- port/raspberrypi/rp2xxx/src/hal/uart.zig | 14 ++++-- 13 files changed, 70 insertions(+), 47 deletions(-) diff --git a/core/src/utilities.zig b/core/src/utilities.zig index 93c937529..37e94d456 100644 --- a/core/src/utilities.zig +++ b/core/src/utilities.zig @@ -656,20 +656,47 @@ pub fn IntFracDiv(int_bits: comptime_int, frac_bits: comptime_int) type { return struct { pub const Int = @Int(.unsigned, int_bits); pub const Frac = @Int(.unsigned, frac_bits); + pub const FixedP = @Int(.unsigned, int_bits + frac_bits); int: Int, - frac: Frac = 0, + frac: Frac, + + /// Writes upper bits to int and lower bits to frac + pub fn from_fixedp(fixedp: FixedP) !@This() { + return if (fixedp >= (1 << frac_bits)) .{ + .int = @intCast(fixedp >> frac_bits), + .frac = @truncate(fixedp), + } else error.DividerTooSmall; + } + + /// Returns clock configuration that most closely matches the given ratio + pub fn from_float(ratio: anytype) @This() { + const info = @typeInfo(@TypeOf(ratio)); + if (info != .float and info != .comptime_float) + @compileError("Expected ratio to be a float, got " ++ @typeName(@TypeOf(ratio))); - // Returns clock configuration that most closely matches the given ratio - pub fn from_ratio(ratio: comptime_float) @This() { - const int = @floor(ratio); - if (int >= (1 << int_bits)) + const fixedp = ratio * (1 << frac_bits); + if (comptime info == .comptime_float and fixedp >= (1 << (int_bits + frac_bits))) @compileError("Divider too big"); - return .{ - .int = @intFromFloat(int), - .frac = (ratio - int) * (1 << frac_bits), - }; + return from_fixedp(@round(fixedp)) catch unreachable; + } + + /// Returns clock configuration that most closely matches the ratio of in/out + pub fn from_ratio(in: comptime_int, out: comptime_int) @This() { + // Maybe use rounding instead of truncating division? + return comptime from_fixedp((in << frac_bits) / out) catch unreachable; + } + + /// Returns a ratio that most closely matches this configuration + pub fn to_float(self: @This(), Float: type) Float { + if (@typeInfo(Float) != .float and @typeInfo(Float) != .comptime_float) + @compileError("Expected return type to be a float, got " ++ @typeName(Float)); + + const int_shifted = @shlExact(@as(FixedP, self.int), frac_bits); + const combined = int_shifted | @as(FixedP, self.frac); + + return @as(Float, @floatFromInt(combined)) / (1 << frac_bits); } }; } diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/tiles.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/tiles.zig index 8c8a161f4..2ad4b0d42 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/tiles.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/tiles.zig @@ -89,7 +89,7 @@ pub fn main() !void { (800_000 * cycles_per_bit); pio.sm_load_and_start_program(sm, ws2812_program, .{ - .clkdiv = rp2xxx.pio.ClkDivOptions.from_float(div), + .clkdiv = .from_float(div), .pin_mappings = .{ .side_set = .single(led_pin), }, diff --git a/examples/raspberrypi/rp2xxx/src/squarewave.zig b/examples/raspberrypi/rp2xxx/src/squarewave.zig index e67a1517e..d36c1e479 100644 --- a/examples/raspberrypi/rp2xxx/src/squarewave.zig +++ b/examples/raspberrypi/rp2xxx/src/squarewave.zig @@ -40,7 +40,7 @@ comptime { pub fn main() !void { pio.gpio_init(pin); pio.sm_load_and_start_program(sm, squarewave_program, .{ - .clkdiv = rp2xxx.pio.ClkDivOptions.from_float(125), + .clkdiv = .from_float(125.0), .pin_mappings = .{ .set = .single(pin), }, diff --git a/examples/raspberrypi/rp2xxx/src/st7789_lcd.zig b/examples/raspberrypi/rp2xxx/src/st7789_lcd.zig index 2b6c4aaf9..f6f242a33 100644 --- a/examples/raspberrypi/rp2xxx/src/st7789_lcd.zig +++ b/examples/raspberrypi/rp2xxx/src/st7789_lcd.zig @@ -98,7 +98,7 @@ pub fn main() !void { led.set_function(.pwm); led.set_direction(.out); led_pwm.slice().set_wrap(100); - led_pwm.slice().set_clk_div(50, 0); + led_pwm.slice().set_clk_div(.from_float(50.0)); led_pwm.set_level(1); led_pwm.slice().enable(); diff --git a/examples/raspberrypi/rp2xxx/src/ws2812.zig b/examples/raspberrypi/rp2xxx/src/ws2812.zig index 7e468056b..953c5030d 100644 --- a/examples/raspberrypi/rp2xxx/src/ws2812.zig +++ b/examples/raspberrypi/rp2xxx/src/ws2812.zig @@ -54,7 +54,7 @@ pub fn main() !void { (800_000 * cycles_per_bit); pio.sm_load_and_start_program(sm, ws2812_program, .{ - .clkdiv = rp2xxx.pio.ClkDivOptions.from_float(div), + .clkdiv = .from_float(div), .pin_mappings = .{ .side_set = .single(led_pin), }, diff --git a/examples/texasinstruments/mspm0/src/uart_echo.zig b/examples/texasinstruments/mspm0/src/uart_echo.zig index b37580e29..fa4c857f3 100644 --- a/examples/texasinstruments/mspm0/src/uart_echo.zig +++ b/examples/texasinstruments/mspm0/src/uart_echo.zig @@ -23,7 +23,7 @@ pub fn main() void { const uart = mspm0.Uart.num(0); uart.configure(.{ - .clk = .{ .div = .from_ratio(3) }, + .clk = .{ .div = .from_float(3.0) }, }); while (true) { diff --git a/examples/texasinstruments/mspm0/src/uart_log.zig b/examples/texasinstruments/mspm0/src/uart_log.zig index 2b5a39600..79d143ec4 100644 --- a/examples/texasinstruments/mspm0/src/uart_log.zig +++ b/examples/texasinstruments/mspm0/src/uart_log.zig @@ -24,7 +24,7 @@ pub fn main() void { uart_tx.set_function(5); const uart = mspm0.Uart.num(0); uart.configure(.{ - .clk = .{ .div = .from_ratio(3) }, + .clk = .{ .div = .from_float(3.0) }, }); mspm0.Uart.init_logger(uart); } diff --git a/port/raspberrypi/rp2xxx/src/hal/adc.zig b/port/raspberrypi/rp2xxx/src/hal/adc.zig index 65bf8d455..879bb5bb1 100644 --- a/port/raspberrypi/rp2xxx/src/hal/adc.zig +++ b/port/raspberrypi/rp2xxx/src/hal/adc.zig @@ -61,6 +61,7 @@ pub fn apply(config: Config) void { if (config.sample_frequency) |sample_frequency| { assert(sample_frequency <= 500_000); + // Shouldn't this be (500_000 << 8) / sample_frequency since a divider of 1 means 500kSa/s? const cycles = (48_000_000 * 256) / @as(u64, sample_frequency); ADC.DIV.write(.{ .FRAC = @as(u8, @truncate(cycles)), diff --git a/port/raspberrypi/rp2xxx/src/hal/cyw43439_pio_spi.zig b/port/raspberrypi/rp2xxx/src/hal/cyw43439_pio_spi.zig index 199a11a88..1e377f9a7 100644 --- a/port/raspberrypi/rp2xxx/src/hal/cyw43439_pio_spi.zig +++ b/port/raspberrypi/rp2xxx/src/hal/cyw43439_pio_spi.zig @@ -80,10 +80,10 @@ pub fn init(config: Config) !Self { pins.clk.set_slew_rate(.fast); try pio.sm_load_and_start_program(sm, cyw43spi_program, .{ - .clkdiv = .{ - // 50MHz is recomended by datasheet - .int = if (hal.compatibility.chip == .RP2040) 2 else 3, - .frac = 0, + // 50MHz is recomended by datasheet + .clkdiv = switch (hal.compatibility.chip) { + .RP2040 => .from_float(2.0), + .RP2350 => .from_float(3.0), }, .pin_mappings = .{ .out = .single(pins.io), diff --git a/port/raspberrypi/rp2xxx/src/hal/cyw43_pio_spi.zig b/port/raspberrypi/rp2xxx/src/hal/cyw43_pio_spi.zig index 3ab95dd2c..08fe53b9b 100644 --- a/port/raspberrypi/rp2xxx/src/hal/cyw43_pio_spi.zig +++ b/port/raspberrypi/rp2xxx/src/hal/cyw43_pio_spi.zig @@ -72,7 +72,7 @@ pub fn init(config: CYW43_PIO_SPI_Config) !CYW43_PIO_SPI { try config.pio.sm_load_and_start_program(sm, cyw43spi_program, .{ // Default max value from pico-sdk 62.5Mhz - .clkdiv = .{ .int = 2, .frac = 0 }, + .clkdiv = .from_float(2.0), .pin_mappings = .{ .out = .single(config.io_pin), .set = .single(config.io_pin), diff --git a/port/raspberrypi/rp2xxx/src/hal/pio/common.zig b/port/raspberrypi/rp2xxx/src/hal/pio/common.zig index 02b61da28..315492531 100644 --- a/port/raspberrypi/rp2xxx/src/hal/pio/common.zig +++ b/port/raspberrypi/rp2xxx/src/hal/pio/common.zig @@ -13,6 +13,7 @@ pub const assembler = @import("assembler.zig"); const encoder = @import("assembler/encoder.zig"); const gpio = @import("../gpio.zig"); +pub const ClkDivOptions = microzig.utilities.IntFracDiv(16, 8); pub const Instruction = encoder.Instruction; pub const Program = assembler.Program; @@ -86,19 +87,6 @@ pub const Irq = enum { }; }; -pub const ClkDivOptions = struct { - int: u16 = 1, - frac: u8 = 0, - - pub fn from_float(div: f32) ClkDivOptions { - const fixed = @as(u24, @intFromFloat(div * 256)); - return ClkDivOptions{ - .int = @as(u16, @truncate(fixed >> 8)), - .frac = @as(u8, @truncate(fixed)), - }; - } -}; - pub const ExecOptions = struct { jmp_pin: ?gpio.Pin = null, wrap: u5 = 31, @@ -629,7 +617,7 @@ pub fn ShiftOptions(chip: Chip) type { pub fn StateMachineInitOptions(chip: Chip) type { return struct { - clkdiv: ClkDivOptions = .{}, + clkdiv: ClkDivOptions = .from_float(1.0), pin_mappings: PinMappingOptions = .{}, exec: ExecOptions = .{}, shift: ShiftOptions(chip) = .{}, @@ -638,7 +626,7 @@ pub fn StateMachineInitOptions(chip: Chip) type { pub fn LoadAndStartProgramOptions(chip: Chip) type { return struct { - clkdiv: ClkDivOptions = .{}, + clkdiv: ClkDivOptions = .from_float(1.0), shift: ShiftOptions(chip) = .{}, pin_mappings: PinMappingOptions = .{}, exec: ExecOptions = .{}, diff --git a/port/raspberrypi/rp2xxx/src/hal/pwm.zig b/port/raspberrypi/rp2xxx/src/hal/pwm.zig index 3f4470c80..3b63c40a8 100644 --- a/port/raspberrypi/rp2xxx/src/hal/pwm.zig +++ b/port/raspberrypi/rp2xxx/src/hal/pwm.zig @@ -8,6 +8,7 @@ const has_rp2350b = compatibility.has_rp2350b; const hw = @import("hw.zig"); pub const Config = struct {}; +pub const FractionalDivider = microzig.utilities.IntFracDiv(8, 4); fn get_regs(slice: u32) *volatile Regs { @import("std").debug.assert(slice < if (has_rp2350b) 12 else 8); @@ -56,10 +57,9 @@ pub const Slice = enum(u32) { /// Set the slice to a clock divider mode. /// /// Parameters: - /// integer - the integer part of the clock divider - /// fraction - the fractional part of the clock divider - pub fn set_clk_div(self: Slice, integer: u8, fraction: u4) void { - set_slice_clk_div(@intFromEnum(self), integer, fraction); + /// div - configuration of the clock divider + pub fn set_clk_div(self: Slice, div: FractionalDivider) void { + set_slice_clk_div(@intFromEnum(self), div); } }; @@ -150,12 +150,11 @@ pub fn set_slice_phase_correct(slice: u32, phase_correct: bool) void { /// /// Parameters: /// slice - the slice to set -/// integer - the integer part of the clock divider -/// fraction - the fractional part of the clock divider -pub fn set_slice_clk_div(slice: u32, integer: u8, fraction: u4) void { +/// div - configuration of the clock divider +pub fn set_slice_clk_div(slice: u32, div: FractionalDivider) void { get_regs(slice).div.modify(.{ - .INT = integer, - .FRAC = fraction, + .INT = div.int, + .FRAC = div.frac, }); } diff --git a/port/raspberrypi/rp2xxx/src/hal/uart.zig b/port/raspberrypi/rp2xxx/src/hal/uart.zig index 55147a1f6..a175e71e7 100644 --- a/port/raspberrypi/rp2xxx/src/hal/uart.zig +++ b/port/raspberrypi/rp2xxx/src/hal/uart.zig @@ -13,6 +13,8 @@ const time = @import("time.zig"); const UartRegs = microzig.chip.types.peripherals.UART0; +pub const BaudrateDivider = microzig.utilities.IntFracDiv(16, 6); + pub const WordBits = enum { five, six, @@ -504,8 +506,8 @@ pub const UART = enum(u1) { }); } + /// Deprecated, use set_divider instead pub fn set_baudrate(uart: UART, baud_rate: u32, peri_freq: u32) void { - const uart_regs = uart.get_regs(); const baud_rate_div = (8 * peri_freq / baud_rate); var baud_ibrd = @as(u16, @intCast(baud_rate_div >> 7)); @@ -517,8 +519,14 @@ pub const UART = enum(u1) { break :baud_fbrd 0; } else @as(u6, @intCast(((@as(u7, @truncate(baud_rate_div))) + 1) / 2)); - uart_regs.UARTIBRD.write(.{ .BAUD_DIVINT = baud_ibrd }); - uart_regs.UARTFBRD.write(.{ .BAUD_DIVFRAC = baud_fbrd }); + uart.set_divider(.{ .int = baud_ibrd, .frac = baud_fbrd }); + } + + /// Remember that there is a builtin by-16 divider + pub fn set_divider(uart: UART, div: BaudrateDivider) void { + const uart_regs = uart.get_regs(); + uart_regs.UARTIBRD.write(.{ .BAUD_DIVINT = div.int }); + uart_regs.UARTFBRD.write(.{ .BAUD_DIVFRAC = div.frac }); // PL011 needs a (dummy) LCR_H write to latch in the divisors. // We don't want to actually change LCR_H contents here. From fe994b47d4937c40964009781527eb5139daba6f Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 14 Jul 2026 04:47:49 +0200 Subject: [PATCH 17/17] Make IntFracDiv a packed struct --- core/src/utilities.zig | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/core/src/utilities.zig b/core/src/utilities.zig index 37e94d456..9083fa18c 100644 --- a/core/src/utilities.zig +++ b/core/src/utilities.zig @@ -653,20 +653,19 @@ test "CircularBuffer bounds" { } pub fn IntFracDiv(int_bits: comptime_int, frac_bits: comptime_int) type { - return struct { + const FixedPoint = @Int(.unsigned, int_bits + frac_bits); + return packed struct(FixedPoint) { pub const Int = @Int(.unsigned, int_bits); pub const Frac = @Int(.unsigned, frac_bits); - pub const FixedP = @Int(.unsigned, int_bits + frac_bits); + pub const FixedP = FixedPoint; - int: Int, frac: Frac, + int: Int, /// Writes upper bits to int and lower bits to frac pub fn from_fixedp(fixedp: FixedP) !@This() { - return if (fixedp >= (1 << frac_bits)) .{ - .int = @intCast(fixedp >> frac_bits), - .frac = @truncate(fixedp), - } else error.DividerTooSmall; + const ret: @This() = @bitCast(fixedp); + return if (ret.int > 0) ret else error.DividerTooSmall; } /// Returns clock configuration that most closely matches the given ratio @@ -688,6 +687,11 @@ pub fn IntFracDiv(int_bits: comptime_int, frac_bits: comptime_int) type { return comptime from_fixedp((in << frac_bits) / out) catch unreachable; } + /// Useful for ratio comparisons + pub fn to_fixedp(self: @This()) FixedP { + return @bitCast(self); + } + /// Returns a ratio that most closely matches this configuration pub fn to_float(self: @This(), Float: type) Float { if (@typeInfo(Float) != .float and @typeInfo(Float) != .comptime_float)