From 730a9a023d87b63a95e5a5ef44bff6113bba333c Mon Sep 17 00:00:00 2001 From: matpala Date: Thu, 15 Oct 2026 13:32:01 -0300 Subject: [PATCH] regz: infer the base of inlined ATDF register groups --- .../atmega/src/hals/atmega328p/interrupts.zig | 9 +- .../atmega/src/hals/atmega328p/spi.zig | 18 +-- tools/regz/src/atdf.zig | 131 +++++++++++++++++- 3 files changed, 136 insertions(+), 22 deletions(-) diff --git a/port/microchip/atmega/src/hals/atmega328p/interrupts.zig b/port/microchip/atmega/src/hals/atmega328p/interrupts.zig index b43806373..a78d74c32 100644 --- a/port/microchip/atmega/src/hals/atmega328p/interrupts.zig +++ b/port/microchip/atmega/src/hals/atmega328p/interrupts.zig @@ -2,10 +2,7 @@ const microzig = @import("microzig"); -// The generated `peripherals.CPU` is a pointer to address 0, so SREG gets its -// own pointer at its offset (see `spi.zig`). -const CPU_Regs = microzig.chip.types.peripherals.CPU; -const SREG: *volatile u8 = @ptrFromInt(@offsetOf(CPU_Regs, "SREG")); +const SREG = µzig.chip.peripherals.CPU.SREG; /// Interrupt state saved by `enter_critical_section`. pub const CriticalSection = struct { @@ -14,14 +11,14 @@ pub const CriticalSection = struct { /// Restores the interrupt state from before `enter_critical_section`. pub inline fn leave(section: CriticalSection) void { asm volatile ("" ::: .{ .memory = true }); - SREG.* = section.sreg; + SREG.write_raw(section.sreg); } }; /// Disables interrupts until `leave` is called on the result. Memory accesses /// aren't moved out of the section. pub inline fn enter_critical_section() CriticalSection { - const section: CriticalSection = .{ .sreg = SREG.* }; + const section: CriticalSection = .{ .sreg = SREG.raw }; asm volatile ("cli" ::: .{ .memory = true }); return section; } diff --git a/port/microchip/atmega/src/hals/atmega328p/spi.zig b/port/microchip/atmega/src/hals/atmega328p/spi.zig index 48e4cc6c9..2e649eb91 100644 --- a/port/microchip/atmega/src/hals/atmega328p/spi.zig +++ b/port/microchip/atmega/src/hals/atmega328p/spi.zig @@ -9,13 +9,7 @@ const std = @import("std"); const microzig = @import("microzig"); const gpio = microzig.hal.gpio; -// The generated `peripherals.SPI` is a pointer to address 0 (not `allowzero`) -// with absolute register offsets, so it can't be used. Each register gets its -// own pointer instead, at its offset (which is its data-space address). -const SPI_Regs = microzig.chip.types.peripherals.SPI; -const SPCR: *volatile @FieldType(SPI_Regs, "SPCR") = @ptrFromInt(@offsetOf(SPI_Regs, "SPCR")); -const SPSR: *volatile @FieldType(SPI_Regs, "SPSR") = @ptrFromInt(@offsetOf(SPI_Regs, "SPSR")); -const SPDR: *volatile u8 = @ptrFromInt(@offsetOf(SPI_Regs, "SPDR")); +const SPI0 = microzig.chip.peripherals.SPI; const ss = gpio.pin(.b, 2); const mosi = gpio.pin(.b, 3); @@ -64,8 +58,8 @@ pub const SPI = enum { mosi.set_direction(.output); sck.set_direction(.output); - SPSR.write_raw(@intFromBool(clock.double_speed)); // SPI2X - SPCR.write(.{ + SPI0.SPSR.write_raw(@intFromBool(clock.double_speed)); // SPI2X + SPI0.SPCR.write(.{ .SPR = @fromBackingInt(@intCast(clock.spr)), .CPHA = @backingInt(config.phase), .CPOL = @backingInt(config.polarity), @@ -169,7 +163,7 @@ test compute_clock { /// Sends `byte` and returns the byte received at the same time. fn transfer_byte(byte: u8) u8 { - SPDR.* = byte; - while (SPSR.read().SPIF == 0) {} - return SPDR.*; + SPI0.SPDR = byte; + while (SPI0.SPSR.read().SPIF == 0) {} + return SPI0.SPDR; } diff --git a/tools/regz/src/atdf.zig b/tools/regz/src/atdf.zig index a4379da5a..f8aa65e6d 100644 --- a/tools/regz/src/atdf.zig +++ b/tools/regz/src/atdf.zig @@ -52,7 +52,7 @@ pub fn load_into_db(db: *Database, doc: xml.Doc) !void { const root = try doc.get_root_element(); var module_it = root.iterate(&.{"modules"}, &.{"module"}); while (module_it.next()) |entry| - try load_module_type(&ctx, entry); + try load_module_type(&ctx, root, entry); var device_it = root.iterate(&.{"devices"}, &.{"device"}); while (device_it.next()) |entry| @@ -283,7 +283,7 @@ fn get_inlined_register_group(parent_node: xml.Node, parent_name: []const u8) ?x } // module instances are listed under atdf-tools-device-file.modules. -fn load_module_type(ctx: *Context, node: xml.Node) !void { +fn load_module_type(ctx: *Context, root: xml.Node, node: xml.Node) !void { validate_attrs(node, &.{ "oldname", "name", @@ -322,6 +322,8 @@ fn load_module_type(ctx: *Context, node: xml.Node) !void { // registers. This operation needs to be done in // `loadModuleInstance()` as well if (get_inlined_register_group(node, name)) |register_group_node| { + if (instances_at_offset_zero(root, name)) + try infer_register_group_offset(ctx, register_group_node, struct_id); try load_register_group_children(ctx, register_group_node, peripheral, struct_id); } else { var register_group_it = node.iterate(&.{}, &.{"register-group"}); @@ -330,6 +332,30 @@ fn load_module_type(ctx: *Context, node: xml.Node) !void { } } +/// True if every instance of the module is at offset 0, so the lowest +/// register offset can be the instance base (SPI on the ATmega328P). +fn instances_at_offset_zero(root: xml.Node, module_name: []const u8) bool { + var device_it = root.iterate(&.{"devices"}, &.{"device"}); + while (device_it.next()) |device_node| { + var module_it = device_node.iterate(&.{"peripherals"}, &.{"module"}); + while (module_it.next()) |module_node| { + const name = module_node.get_attribute("name") orelse continue; + if (!std.mem.eql(u8, name, module_name)) continue; + + var instance_it = module_node.iterate(&.{}, &.{"instance"}); + while (instance_it.next()) |instance_node| { + var register_group_it = instance_node.iterate(&.{}, &.{"register-group"}); + while (register_group_it.next()) |register_group_node| { + const offset = register_group_node.get_attribute_int(u64, "offset") catch return false; + if (offset != 0) return false; + } + } + } + } + + return true; +} + fn load_module_interrupt_group(ctx: *Context, node: xml.Node) !void { const name = node.get_attribute("name") orelse return error.MissingInterruptGroupName; try ctx.interrupt_groups.put(ctx.db.gpa, name, .empty); @@ -822,10 +848,14 @@ fn load_module_instance_from_peripheral( const name = node.get_attribute("name") orelse return error.MissingInstanceName; const desc = node.get_attribute("caption"); + const struct_id = try db.get_peripheral_struct(peripheral_id); const offset_bytes: u64 = if (get_inlined_register_group(node, name)) |register_group_node| blk: { log.debug("inlining {s}", .{name}); const offset = try register_group_node.get_attribute_int(u64, "offset") orelse return error.MissingPeripheralOffset; - break :blk offset; + break :blk if (offset == 0) + ctx.inferred_register_group_offsets.get(struct_id) orelse offset + else + offset; } else { return error.Todo; //unreachable; @@ -836,7 +866,6 @@ fn load_module_instance_from_peripheral( // }; }; - const struct_id = try db.get_peripheral_struct(peripheral_id); const instance_id = try db.create_device_peripheral(device_id, .{ .struct_id = struct_id, .name = name, @@ -1310,6 +1339,100 @@ test "atdf.instance of register group" { try expectEqual(@as(u64, 0x0), pinb.offset_bytes); } +test "atdf.inlined register group with absolute offsets" { + const text = + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + ; + + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + const doc = try xml.Doc.from_memory(text); + var db = try Database.create_from_doc(std.testing.allocator, .atdf, doc); + defer db.destroy(); + + const device_id = try db.get_device_id_by_name("ATmega328P") orelse return error.NoDevice; + + const spi_instance = try db.get_device_peripheral_by_name(arena.allocator(), device_id, "SPI"); + try expectEqual(@as(u64, 0x4C), spi_instance.offset_bytes); + + const spcr = try db.get_register_by_name(arena.allocator(), spi_instance.struct_id, "SPCR"); + try expectEqual(@as(u64, 0x0), spcr.offset_bytes); + + const spdr = try db.get_register_by_name(arena.allocator(), spi_instance.struct_id, "SPDR"); + try expectEqual(@as(u64, 0x2), spdr.offset_bytes); +} + +test "atdf.inlined register group with relative offsets" { + const text = + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + ; + + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + const doc = try xml.Doc.from_memory(text); + var db = try Database.create_from_doc(std.testing.allocator, .atdf, doc); + defer db.destroy(); + + const device_id = try db.get_device_id_by_name("ATtiny1616") orelse return error.NoDevice; + + const syscfg_instance = try db.get_device_peripheral_by_name(arena.allocator(), device_id, "SYSCFG"); + try expectEqual(@as(u64, 0xF00), syscfg_instance.offset_bytes); + + const revid = try db.get_register_by_name(arena.allocator(), syscfg_instance.struct_id, "REVID"); + try expectEqual(@as(u64, 0x1), revid.offset_bytes); +} + test "atdf.interrupts" { const text = \\