From a34e4ef648e53bd7db026295bee09ca6a190be65 Mon Sep 17 00:00:00 2001 From: Matt Knight Date: Sat, 31 Dec 2022 15:39:55 -0800 Subject: [PATCH] initial support for dimElementsGroup for SVD (#70) --- tools/regz/src/Database.zig | 92 ++++++++--- tools/regz/src/atdf.zig | 4 +- tools/regz/src/gen.zig | 309 ++++++++++++++++++++++++++---------- tools/regz/src/svd.zig | 307 +++++++++++++++++++++++++---------- 4 files changed, 521 insertions(+), 191 deletions(-) diff --git a/tools/regz/src/Database.zig b/tools/regz/src/Database.zig index 67d10da..2b690ae 100644 --- a/tools/regz/src/Database.zig +++ b/tools/regz/src/Database.zig @@ -9,7 +9,7 @@ attrs: struct { description: HashMap(EntityId, []const u8) = .{}, offset: HashMap(EntityId, u64) = .{}, access: HashMap(EntityId, Access) = .{}, - repeated: HashMap(EntityId, u64) = .{}, + count: HashMap(EntityId, u64) = .{}, size: HashMap(EntityId, u64) = .{}, reset_value: HashMap(EntityId, u64) = .{}, reset_mask: HashMap(EntityId, u64) = .{}, @@ -119,7 +119,7 @@ pub fn deinit(db: *Database) void { db.attrs.description.deinit(db.gpa); db.attrs.offset.deinit(db.gpa); db.attrs.access.deinit(db.gpa); - db.attrs.repeated.deinit(db.gpa); + db.attrs.count.deinit(db.gpa); db.attrs.size.deinit(db.gpa); db.attrs.reset_value.deinit(db.gpa); db.attrs.reset_mask.deinit(db.gpa); @@ -209,8 +209,36 @@ pub fn createEntity(db: *Database) EntityId { } pub fn destroyEntity(db: *Database, id: EntityId) void { - _ = db; - _ = id; + switch (db.getEntityType(id) orelse return) { + .register => { + log.debug("{}: destroying register", .{id}); + if (db.attrs.parent.get(id)) |parent_id| { + if (db.children.registers.getEntry(parent_id)) |entry| { + _ = entry.value_ptr.swapRemove(id); + } + } + + // if has a parent, remove it from the set + // remove all attributes + + // TODO: remove fields + _ = db.types.registers.swapRemove(id); + }, + else => {}, + } + + db.removeAttrs(id); +} + +fn removeAttrs(db: *Database, id: EntityId) void { + inline for (@typeInfo(TypeOfField(Database, "attrs")).Struct.fields) |field| { + if (@hasDecl(field.type, "swapRemove")) + _ = @field(db.attrs, field.name).swapRemove(id) + else if (@hasDecl(field.type, "remove")) + _ = @field(db.attrs, field.name).remove(id) + else + unreachable; + } } pub fn createDevice( @@ -239,6 +267,8 @@ pub fn createPeripheralInstance( name: []const u8, // required for now offset: u64, + // count for an array + count: ?u64 = null, }, ) !EntityId { assert(db.entityIs("instance.device", device_id)); @@ -253,6 +283,9 @@ pub fn createPeripheralInstance( try db.addName(id, opts.name); try db.addOffset(id, opts.offset); + if (opts.count) |c| + try db.addCount(id, c); + try db.addChild("instance.peripheral", device_id, id); return id; } @@ -260,7 +293,8 @@ pub fn createPeripheralInstance( pub fn createPeripheral( db: *Database, opts: struct { - name: ?[]const u8 = null, + name: []const u8, + size: ?u64 = null, }, ) !EntityId { const id = db.createEntity(); @@ -269,8 +303,10 @@ pub fn createPeripheral( log.debug("{}: creating peripheral", .{id}); try db.types.peripherals.put(db.gpa, id, {}); - if (opts.name) |n| - try db.addName(id, n); + try db.addName(id, opts.name); + + if (opts.size) |s| + try db.addSize(id, s); return id; } @@ -303,9 +339,14 @@ pub fn createRegister( name: []const u8, description: ?[]const u8 = null, /// offset is in bytes - offset: ?u64 = null, + offset: u64, /// size is in bits - size: ?u64 = null, + size: u64, + /// count if there is an array + count: ?u64 = null, + access: ?Access = null, + reset_mask: ?u64 = null, + reset_value: ?u64 = null, }, ) !EntityId { assert(db.entityIs("type.peripheral", parent_id) or @@ -321,11 +362,20 @@ pub fn createRegister( if (opts.description) |d| try db.addDescription(id, d); - if (opts.offset) |o| - try db.addOffset(id, o); + try db.addOffset(id, opts.offset); + try db.addSize(id, opts.size); - if (opts.size) |s| - try db.addSize(id, s); + if (opts.count) |c| + try db.addCount(id, c); + + if (opts.access) |a| + try db.addAccess(id, a); + + if (opts.reset_mask) |rm| + try db.addResetMask(id, rm); + + if (opts.reset_value) |rv| + try db.addResetValue(id, rv); try db.addChild("type.register", parent_id, id); @@ -385,7 +435,6 @@ pub fn createEnum( size: ?u64 = null, }, ) !EntityId { - // TODO: other parent types assert(db.entityIs("type.peripheral", parent_id) or db.entityIs("type.field", parent_id)); @@ -476,7 +525,7 @@ pub fn addSize(db: *Database, id: EntityId, size: u64) !void { } pub fn addOffset(db: *Database, id: EntityId, offset: u64) !void { - log.debug("{}: adding offset: {}", .{ id, offset }); + log.debug("{}: adding offset: 0x{x}", .{ id, offset }); try db.attrs.offset.putNoClobber(db.gpa, id, offset); } @@ -486,7 +535,7 @@ pub fn addResetValue(db: *Database, id: EntityId, reset_value: u64) !void { } pub fn addResetMask(db: *Database, id: EntityId, reset_mask: u64) !void { - log.debug("{}: adding register mask: 0x{}", .{ id, reset_mask }); + log.debug("{}: adding register mask: 0x{x}", .{ id, reset_mask }); try db.attrs.reset_mask.putNoClobber(db.gpa, id, reset_mask); } @@ -495,6 +544,11 @@ pub fn addAccess(db: *Database, id: EntityId, access: Access) !void { try db.attrs.access.putNoClobber(db.gpa, id, access); } +pub fn addCount(db: *Database, id: EntityId, count: u64) !void { + log.debug("{}: adding count: {}", .{ id, count }); + try db.attrs.count.putNoClobber(db.gpa, id, count); +} + pub fn addChild( db: *Database, comptime entity_location: []const u8, @@ -562,10 +616,12 @@ pub fn getEntityIdByName( comptime var group = (tok_it.next() orelse unreachable) ++ "s"; comptime var table = (tok_it.next() orelse unreachable) ++ "s"; + log.debug("group: {s}, table: {s}", .{ group, table }); var it = @field(@field(db, group), table).iterator(); return while (it.next()) |entry| { const entry_id = entry.key_ptr.*; const entry_name = db.attrs.name.get(entry_id) orelse continue; + log.debug("looking at name: {s}", .{entry_name}); if (std.mem.eql(u8, name, entry_name)) { assert(db.entityIs(entity_location, entry_id)); return entry_id; @@ -589,7 +645,7 @@ pub const EntityType = enum { pub fn getEntityType( db: Database, id: EntityId, -) EntityType { +) ?EntityType { inline for (@typeInfo(TypeOfField(Database, "types")).Struct.fields) |field| { if (@field(db.types, field.name).contains(id)) return @field(EntityType, field.name[0 .. field.name.len - 1]); @@ -603,7 +659,7 @@ pub fn getEntityType( @field(EntityType, field.name[0 .. field.name.len - 1]); } - @panic("unhandled entity type"); + return null; } // assert that the database is in valid state diff --git a/tools/regz/src/atdf.zig b/tools/regz/src/atdf.zig index 9d4d847..4a8601d 100644 --- a/tools/regz/src/atdf.zig +++ b/tools/regz/src/atdf.zig @@ -459,11 +459,11 @@ fn loadRegister( .size = if (node.getAttribute("size")) |size_str| @as(u64, 8) * try std.fmt.parseInt(u64, size_str, 0) else - null, + return error.MissingRegisterSize, .offset = if (node.getAttribute("offset")) |offset_str| try std.fmt.parseInt(u64, offset_str, 0) else - null, + return error.MissingRegisterOffset, }); errdefer db.destroyEntity(id); diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 6f94c32..074af7d 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -209,8 +209,15 @@ fn writePeripheralInstance(db: Database, instance_id: EntityId, offset: u64, out else if (db.attrs.description.get(type_id)) |description| try writeComment(db.arena.allocator(), description, writer); - try writer.print("pub const {s} = @ptrCast(*volatile {s}, 0x{x});\n", .{ + var array_prefix_buf: [80]u8 = undefined; + const array_prefix = if (db.attrs.count.get(instance_id)) |count| + try std.fmt.bufPrint(&array_prefix_buf, "[{}]", .{count}) + else + ""; + + try writer.print("pub const {s} = @ptrCast(*volatile {s}{s}, 0x{x});\n", .{ std.zig.fmtId(name), + array_prefix, type_ref, offset, }); @@ -282,27 +289,11 @@ fn isPeripheralZeroSized(db: Database, peripheral_id: EntityId) bool { } else true; } -// have to fill this in manually when new errors are introduced. This is because writePeripheral is recursive via writePeripheralBase -const ErrorWritePeripheralBase = error{ - OutOfMemory, - InvalidCharacter, - Overflow, - NameNotFound, - MissingEnumSize, - MissingEnumFields, - MissingEnumFieldName, - MissingEnumFieldValue, -}; - -fn WritePeripheralError(comptime Writer: type) type { - return ErrorWritePeripheralBase || Writer.Error; -} - fn writePeripheral( db: Database, peripheral_id: EntityId, out_writer: anytype, -) WritePeripheralError(@TypeOf(out_writer))!void { +) !void { assert(db.entityIs("type.peripheral", peripheral_id) or db.entityIs("type.register_group", peripheral_id)); @@ -339,7 +330,7 @@ fn writePeripheral( \\ , .{ std.zig.fmtId(name), - if (zero_sized) "" else "packed", + if (zero_sized) "" else "extern", if (has_modes) "union" else "struct", }); @@ -561,7 +552,7 @@ fn writeRegistersWithModes( } else try moded_registers.append(register); } - try writer.print("{s}: packed struct {{\n", .{ + try writer.print("{s}: extern struct {{\n", .{ std.zig.fmtId(mode_name), }); @@ -578,8 +569,6 @@ fn writeRegistersBase( registers: []const EntityWithOffset, out_writer: anytype, ) !void { - _ = parent_id; - // registers _should_ be sorted when then make their way here assert(std.sort.isSorted(EntityWithOffset, registers, {}, EntityWithOffset.lessThan)); var buffer = std.ArrayList(u8).init(db.arena.allocator()); @@ -589,45 +578,61 @@ fn writeRegistersBase( // don't have to care about modes // prioritize smaller fields that come earlier - { - var offset: u64 = 0; - var i: u32 = 0; - - while (i < registers.len) { - if (offset < registers[i].offset) { - try writer.print("reserved{}: [{}]u8,\n", .{ registers[i].offset, registers[i].offset - offset }); - offset = registers[i].offset; - } else if (offset > registers[i].offset) { - if (db.attrs.name.get(registers[i].id)) |name| - log.warn("skipping register: {s}", .{name}); - - i += 1; - continue; + var offset: u64 = 0; + var i: u32 = 0; + + while (i < registers.len) { + if (offset < registers[i].offset) { + try writer.print("reserved{}: [{}]u8,\n", .{ registers[i].offset, registers[i].offset - offset }); + offset = registers[i].offset; + } else if (offset > registers[i].offset) { + if (db.attrs.name.get(registers[i].id)) |name| + log.warn("skipping register: {s}", .{name}); + + i += 1; + continue; + } + + var end = i; + while (end < registers.len and registers[end].offset == offset) : (end += 1) {} + const next = blk: { + var ret: ?EntityWithOffsetAndSize = null; + for (registers[i..end]) |register| { + const size = if (db.attrs.size.get(register.id)) |size| + if (db.attrs.count.get(register.id)) |count| + size * count + else + size + else + unreachable; + + if (ret == null or (size < ret.?.size)) + ret = .{ + .id = register.id, + .offset = register.offset, + .size = size, + }; } - var end = i; - while (end < registers.len and registers[end].offset == offset) : (end += 1) {} - const next = blk: { - var ret: ?EntityWithOffsetAndSize = null; - for (registers[i..end]) |register| { - const size = db.attrs.size.get(register.id) orelse unreachable; - if (ret == null or (size < ret.?.size)) - ret = .{ - .id = register.id, - .offset = register.offset, - .size = size, - }; - } + break :blk ret orelse unreachable; + }; - break :blk ret orelse unreachable; - }; + try writeRegister(db, next.id, writer); + // TODO: round up to next power of two + assert(next.size % 8 == 0); + offset += next.size / 8; + i = end; + } - try writeRegister(db, next.id, writer); - // TODO: round up to next power of two - assert(next.size % 8 == 0); - offset += next.size / 8; - i = end; - } + // TODO: name collision + if (db.attrs.size.get(parent_id)) |size| { + if (offset > size) + @panic("peripheral size too small, parsing should have caught this"); + + if (offset != size) + try writer.print("padding: [{}]u8,\n", .{ + size - offset, + }); } try out_writer.writeAll(buffer.items); @@ -648,6 +653,12 @@ fn writeRegister( if (db.attrs.description.get(register_id)) |description| try writeComment(db.arena.allocator(), description, writer); + var array_prefix_buf: [80]u8 = undefined; + const array_prefix = if (db.attrs.count.get(register_id)) |count| + try std.fmt.bufPrint(&array_prefix_buf, "[{}]", .{count}) + else + ""; + if (db.children.fields.get(register_id)) |field_set| { var fields = std.ArrayList(EntityWithOffset).init(db.gpa); defer fields.deinit(); @@ -662,14 +673,19 @@ fn writeRegister( } std.sort.sort(EntityWithOffset, fields.items, {}, EntityWithOffset.lessThan); - try writer.print("{s}: mmio.Mmio({}, packed struct{{\n", .{ + try writer.print("{s}: mmio.Mmio({}, {s}packed struct{{\n", .{ std.zig.fmtId(name), size, + array_prefix, }); try writeFields(db, fields.items, size, writer); try writer.writeAll("}),\n"); - } else try writer.print("{s}: u{},\n", .{ std.zig.fmtId(name), size }); + } else try writer.print("{s}: {s}u{},\n", .{ + std.zig.fmtId(name), + array_prefix, + size, + }); try out_writer.writeAll(buffer.items); } @@ -819,7 +835,7 @@ test "gen.peripheral type with register and field" { \\const mmio = @import("mmio"); \\ \\pub const types = struct { - \\ pub const TEST_PERIPHERAL = packed struct { + \\ pub const TEST_PERIPHERAL = extern struct { \\ TEST_REGISTER: mmio.Mmio(32, packed struct { \\ TEST_FIELD: u1, \\ padding: u31 = 0, @@ -873,7 +889,7 @@ test "gen.peripheral instantiation" { \\}; \\ \\pub const types = struct { - \\ pub const TEST_PERIPHERAL = packed struct { + \\ pub const TEST_PERIPHERAL = extern struct { \\ TEST_REGISTER: mmio.Mmio(32, packed struct { \\ TEST_FIELD: u1, \\ padding: u31 = 0, @@ -926,7 +942,7 @@ test "gen.peripherals with a shared type" { \\}; \\ \\pub const types = struct { - \\ pub const TEST_PERIPHERAL = packed struct { + \\ pub const TEST_PERIPHERAL = extern struct { \\ TEST_REGISTER: mmio.Mmio(32, packed struct { \\ TEST_FIELD: u1, \\ padding: u31 = 0, @@ -994,7 +1010,7 @@ test "gen.peripheral with modes" { \\const mmio = @import("mmio"); \\ \\pub const types = struct { - \\ pub const TEST_PERIPHERAL = packed union { + \\ pub const TEST_PERIPHERAL = extern union { \\ pub const Mode = enum { \\ TEST_MODE1, \\ TEST_MODE2, @@ -1019,14 +1035,14 @@ test "gen.peripheral with modes" { \\ unreachable; \\ } \\ - \\ TEST_MODE1: packed struct { + \\ TEST_MODE1: extern struct { \\ TEST_REGISTER1: u32, \\ COMMON_REGISTER: mmio.Mmio(32, packed struct { \\ TEST_FIELD: u1, \\ padding: u31 = 0, \\ }), \\ }, - \\ TEST_MODE2: packed struct { + \\ TEST_MODE2: extern struct { \\ TEST_REGISTER2: u32, \\ COMMON_REGISTER: mmio.Mmio(32, packed struct { \\ TEST_FIELD: u1, @@ -1069,7 +1085,7 @@ test "gen.peripheral with enum" { \\const mmio = @import("mmio"); \\ \\pub const types = struct { - \\ pub const TEST_PERIPHERAL = packed struct { + \\ pub const TEST_PERIPHERAL = extern struct { \\ pub const TEST_ENUM = enum(u4) { \\ TEST_ENUM_FIELD1 = 0x0, \\ TEST_ENUM_FIELD2 = 0x1, @@ -1113,7 +1129,7 @@ test "gen.peripheral with enum, enum is exhausted of values" { \\const mmio = @import("mmio"); \\ \\pub const types = struct { - \\ pub const TEST_PERIPHERAL = packed struct { + \\ pub const TEST_PERIPHERAL = extern struct { \\ pub const TEST_ENUM = enum(u1) { \\ TEST_ENUM_FIELD1 = 0x0, \\ TEST_ENUM_FIELD2 = 0x1, @@ -1163,7 +1179,7 @@ test "gen.field with named enum" { \\const mmio = @import("mmio"); \\ \\pub const types = struct { - \\ pub const TEST_PERIPHERAL = packed struct { + \\ pub const TEST_PERIPHERAL = extern struct { \\ pub const TEST_ENUM = enum(u4) { \\ TEST_ENUM_FIELD1 = 0x0, \\ TEST_ENUM_FIELD2 = 0x1, @@ -1219,7 +1235,7 @@ test "gen.field with anonymous enum" { \\const mmio = @import("mmio"); \\ \\pub const types = struct { - \\ pub const TEST_PERIPHERAL = packed struct { + \\ pub const TEST_PERIPHERAL = extern struct { \\ TEST_REGISTER: mmio.Mmio(8, packed struct { \\ TEST_FIELD: packed union { \\ raw: u4, @@ -1281,13 +1297,13 @@ test "gen.namespaced register groups" { \\ \\pub const types = struct { \\ pub const PORT = struct { - \\ pub const PORTB = packed struct { + \\ pub const PORTB = extern struct { \\ PORTB: u8, \\ DDRB: u8, \\ PINB: u8, \\ }; \\ - \\ pub const PORTC = packed struct { + \\ pub const PORTC = extern struct { \\ PORTC: u8, \\ DDRC: u8, \\ PINC: u8, @@ -1332,7 +1348,7 @@ test "gen.peripheral with reserved register" { \\}; \\ \\pub const types = struct { - \\ pub const PORTB = packed struct { + \\ pub const PORTB = extern struct { \\ PORTB: u32, \\ reserved8: [4]u8, \\ PINB: u32, @@ -1342,19 +1358,136 @@ test "gen.peripheral with reserved register" { , buffer.items); } -// TODO: -// - write some tests regarding register scoped modes, and what fields look like -// when they have a peripheral/register_group scoped mode -// - default values should be reset value, otherwise 0 -// - -// more test ideas: -// - interrupts -// - anonymous peripherals -// - multiple register groups -// - access -// - modes -// - repeated -// - ordered address printing -// - modes with discontiguous bits -// +test "gen.peripheral with count" { + var db = try Database.init(std.testing.allocator); + defer db.deinit(); + + const device_id = try db.createDevice(.{ .name = "ATmega328P" }); + + const peripheral_id = try db.createPeripheral(.{ + .name = "PORTB", + .size = 3, + }); + + _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ + .name = "PORTB", + .offset = 0x23, + .count = 4, + }); + + _ = try db.createRegister(peripheral_id, .{ .name = "PORTB", .size = 8, .offset = 0 }); + _ = try db.createRegister(peripheral_id, .{ .name = "DDRB", .size = 8, .offset = 1 }); + _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 8, .offset = 2 }); + + var buffer = std.ArrayList(u8).init(std.testing.allocator); + defer buffer.deinit(); + + try db.toZig(buffer.writer()); + try std.testing.expectEqualStrings( + \\const mmio = @import("mmio"); + \\ + \\pub const devices = struct { + \\ pub const ATmega328P = struct { + \\ pub const PORTB = @ptrCast(*volatile [4]types.PORTB, 0x23); + \\ }; + \\}; + \\ + \\pub const types = struct { + \\ pub const PORTB = extern struct { + \\ PORTB: u8, + \\ DDRB: u8, + \\ PINB: u8, + \\ }; + \\}; + \\ + , buffer.items); +} + +test "gen.peripheral with count, padding required" { + var db = try Database.init(std.testing.allocator); + defer db.deinit(); + + const device_id = try db.createDevice(.{ .name = "ATmega328P" }); + + const peripheral_id = try db.createPeripheral(.{ + .name = "PORTB", + .size = 4, + }); + + _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ + .name = "PORTB", + .offset = 0x23, + .count = 4, + }); + + _ = try db.createRegister(peripheral_id, .{ .name = "PORTB", .size = 8, .offset = 0 }); + _ = try db.createRegister(peripheral_id, .{ .name = "DDRB", .size = 8, .offset = 1 }); + _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 8, .offset = 2 }); + + var buffer = std.ArrayList(u8).init(std.testing.allocator); + defer buffer.deinit(); + + try db.toZig(buffer.writer()); + try std.testing.expectEqualStrings( + \\const mmio = @import("mmio"); + \\ + \\pub const devices = struct { + \\ pub const ATmega328P = struct { + \\ pub const PORTB = @ptrCast(*volatile [4]types.PORTB, 0x23); + \\ }; + \\}; + \\ + \\pub const types = struct { + \\ pub const PORTB = extern struct { + \\ PORTB: u8, + \\ DDRB: u8, + \\ PINB: u8, + \\ padding: [1]u8, + \\ }; + \\}; + \\ + , buffer.items); +} + +test "gen.register with count" { + var db = try Database.init(std.testing.allocator); + defer db.deinit(); + + const device_id = try db.createDevice(.{ .name = "ATmega328P" }); + + const peripheral_id = try db.createPeripheral(.{ + .name = "PORTB", + }); + + _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ + .name = "PORTB", + .offset = 0x23, + }); + + _ = try db.createRegister(peripheral_id, .{ .name = "PORTB", .size = 8, .offset = 0, .count = 4 }); + _ = try db.createRegister(peripheral_id, .{ .name = "DDRB", .size = 8, .offset = 4 }); + _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 8, .offset = 5 }); + + var buffer = std.ArrayList(u8).init(std.testing.allocator); + defer buffer.deinit(); + + try db.toZig(buffer.writer()); + try std.testing.expectEqualStrings( + \\const mmio = @import("mmio"); + \\ + \\pub const devices = struct { + \\ pub const ATmega328P = struct { + \\ pub const PORTB = @ptrCast(*volatile types.PORTB, 0x23); + \\ }; + \\}; + \\ + \\pub const types = struct { + \\ pub const PORTB = extern struct { + \\ PORTB: [4]u8, + \\ DDRB: u8, + \\ PINB: u8, + \\ }; + \\}; + \\ + , buffer.items); +} diff --git a/tools/regz/src/svd.zig b/tools/regz/src/svd.zig index f01a1fa..ec34170 100644 --- a/tools/regz/src/svd.zig +++ b/tools/regz/src/svd.zig @@ -27,46 +27,19 @@ const Context = struct { log.debug("{}: derived from '{s}'", .{ id, derived_from }); } - fn getRegisterPropsDerivedFromParent( + fn deriveRegisterPropertiesFrom( ctx: *Context, - id: EntityId, node: xml.Node, + from: EntityId, ) !RegisterProperties { const register_props = try RegisterProperties.parse(node); - try ctx.addRegisterPropertiesDerivedFromParent(id, register_props); - - return ctx.register_props.get(id).?; - } - - fn addRegisterPropertiesDerivedFrom( - ctx: *Context, - id: EntityId, - from: EntityId, - register_props: RegisterProperties, - ) !void { - const db = ctx.db; var base_register_props = ctx.register_props.get(from) orelse unreachable; - inline for (@typeInfo(RegisterProperties).Struct.fields) |field| { if (@field(register_props, field.name)) |value| @field(base_register_props, field.name) = value; } - log.debug("deriving register props: {}", .{base_register_props}); - try ctx.register_props.put(db.gpa, id, base_register_props); - } - - fn addRegisterPropertiesDerivedFromParent( - ctx: *Context, - id: EntityId, - register_props: RegisterProperties, - ) !void { - const db = ctx.db; - - if (db.attrs.parent.get(id)) |parent_id| - try ctx.addRegisterPropertiesDerivedFrom(id, parent_id, register_props) - else - try ctx.register_props.put(db.gpa, id, register_props); + return base_register_props; } }; @@ -154,7 +127,6 @@ pub fn loadIntoDb(db: *Database, doc: xml.Doc) !void { defer ctx.deinit(); const register_props = try RegisterProperties.parse(root); - log.debug("parsed register props: {}", .{register_props}); try ctx.register_props.put(db.gpa, device_id, register_props); var peripheral_it = root.iterate(&.{"peripherals"}, "peripheral"); @@ -176,53 +148,54 @@ pub fn loadIntoDb(db: *Database, doc: xml.Doc) !void { pub fn deriveEntity(db: Database, id: EntityId, derived_name: []const u8) !void { log.debug("{}: derived from {s}", .{ id, derived_name }); const entity_type = db.getEntityType(id); - log.warn("TODO: implement derivation for {}", .{entity_type}); + log.warn("TODO: implement derivation for {?}", .{entity_type}); } pub fn loadPeripheral(ctx: *Context, node: xml.Node, device_id: EntityId) !void { const db = ctx.db; - const name = node.getValue("name") orelse return error.PeripheralMissingName; - const base_address = node.getValue("baseAddress") orelse return error.PeripheralMissingBaseAddress; - const offset = try std.fmt.parseInt(u64, base_address, 0); - // dim elements before creation as it might require creating multiple instances - const dim_elements = try DimElements.parse(node); - if (dim_elements != null) - return error.TodoDimElements; - - const type_id = try db.createPeripheral(.{ - .name = name, - }); + const type_id = try loadPeripheralType(ctx, node); errdefer db.destroyEntity(type_id); const instance_id = try db.createPeripheralInstance(device_id, type_id, .{ - .name = name, - .offset = offset, + .name = node.getValue("name") orelse return error.PeripheralMissingName, + .offset = if (node.getValue("baseAddress")) |base_address| + try std.fmt.parseInt(u64, base_address, 0) + else + return error.PeripheralMissingBaseAddress, }); errdefer db.destroyEntity(instance_id); - if (node.findChild("interrupt")) |interrupt_node| - try loadInterrupt(db, interrupt_node, device_id); + const dim_elements = try DimElements.parse(node); + if (dim_elements) |elements| { + // peripherals can't have dimIndex set according to the spec + if (elements.dim_index != null) + return error.Malformed; - // TODO: skip if: - // - any dimElementGroup values are set + if (elements.dim_name != null) + return error.TodoDimElementsExtended; - log.debug("{}: created peripheral instance", .{instance_id}); + // count is applied to the specific instance + try db.addCount(instance_id, elements.dim); - if (node.getValue("description")) |description| { - try db.addDescription(type_id, description); - try db.addDescription(instance_id, description); + // size is applied to the type + try db.addSize(type_id, elements.dim_increment); } + if (node.findChild("interrupt")) |interrupt_node| + try loadInterrupt(db, interrupt_node, device_id); + + if (node.getValue("description")) |description| + try db.addDescription(instance_id, description); + if (node.getValue("version")) |version| try db.addVersion(instance_id, version); if (node.getAttribute("derivedFrom")) |derived_from| try ctx.addDerivedEntity(instance_id, derived_from); - const register_props = try RegisterProperties.parse(node); - log.debug("parsed register props: {}", .{register_props}); - try ctx.addRegisterPropertiesDerivedFrom(type_id, device_id, register_props); + const register_props = try ctx.deriveRegisterPropertiesFrom(node, device_id); + try ctx.register_props.put(db.gpa, type_id, register_props); var register_it = node.iterate(&.{"registers"}, "register"); while (register_it.next()) |register_node| @@ -235,7 +208,6 @@ pub fn loadPeripheral(ctx: *Context, node: xml.Node, device_id: EntityId) !void loadCluster(ctx, cluster_node, type_id) catch |err| log.warn("failed to load cluster: {}", .{err}); - // dimElementGroup // alternatePeripheral // groupName // prependToName @@ -245,6 +217,20 @@ pub fn loadPeripheral(ctx: *Context, node: xml.Node, device_id: EntityId) !void // addressBlock } +fn loadPeripheralType(ctx: *Context, node: xml.Node) !EntityId { + const db = ctx.db; + + const id = try db.createPeripheral(.{ + .name = node.getValue("name") orelse return error.PeripheralMissingName, + }); + errdefer db.destroyEntity(id); + + if (node.getValue("description")) |description| + try db.addDescription(id, description); + + return id; +} + fn loadInterrupt(db: *Database, node: xml.Node, device_id: EntityId) !void { assert(db.entityIs("instance.device", device_id)); @@ -292,34 +278,34 @@ fn loadRegister( parent_id: EntityId, ) !void { const db = ctx.db; + + const register_props = try ctx.deriveRegisterPropertiesFrom(node, parent_id); + const size = register_props.size orelse return error.MissingRegisterSize; + const count: ?u64 = if (try DimElements.parse(node)) |elements| count: { + if (elements.dim_index != null or elements.dim_name != null) + return error.TodoDimElementsExtended; + + if ((elements.dim_increment * 8) != size) + return error.DimIncrementSizeMismatch; + + break :count elements.dim; + } else null; + const id = try db.createRegister(parent_id, .{ .name = node.getValue("name") orelse return error.MissingRegisterName, .description = node.getValue("description"), .offset = if (node.getValue("addressOffset")) |offset_str| try std.fmt.parseInt(u64, offset_str, 0) else - null, + return error.MissingRegisterOffset, + .size = size, + .count = count, + .access = register_props.access, + .reset_mask = register_props.reset_mask, + .reset_value = register_props.reset_value, }); errdefer db.destroyEntity(id); - const dim_elements = try DimElements.parse(node); - if (dim_elements != null) - return error.TodoDimElements; - - const register_props = try ctx.getRegisterPropsDerivedFromParent(id, node); - if (register_props.size) |size| - try db.addSize(id, size); - - // TODO: protection - if (register_props.access) |access| - try db.addAccess(id, access); - - if (register_props.reset_mask) |reset_mask| - try db.addResetMask(id, reset_mask); - - if (register_props.reset_value) |reset_value| - try db.addResetValue(id, reset_value); - var field_it = node.iterate(&.{"fields"}, "field"); while (field_it.next()) |field_node| loadField(ctx, field_node, id) catch |err| @@ -381,7 +367,7 @@ fn loadEnumeratedValues(ctx: *Context, node: xml.Node, field_id: EntityId) !void .name = node.getValue("name"), .size = db.attrs.size.get(field_id), }); - defer db.destroyEntity(id); + errdefer db.destroyEntity(id); try db.attrs.@"enum".putNoClobber(db.gpa, field_id, id); @@ -402,7 +388,7 @@ fn loadEnumeratedValue(ctx: *Context, node: xml.Node, enum_id: EntityId) !void { else return error.EnumFieldMissingValue, }); - defer db.destroyEntity(id); + errdefer db.destroyEntity(id); } pub const Revision = struct { @@ -910,7 +896,7 @@ fn parseAccess(str: []const u8) !Access { error.UnknownAccessType; } -test "device register properties" { +test "svd.device register properties" { const text = \\ \\ TEST_DEVICE @@ -925,6 +911,7 @@ test "device register properties" { \\ \\ \\ TEST_REGISTER + \\ 0 \\ \\ \\ @@ -948,7 +935,7 @@ test "device register properties" { try expectAttr(db, "reset_mask", 0xffffffff, register_id); } -test "peripheral register properties" { +test "svd.peripheral register properties" { const text = \\ \\ TEST_DEVICE @@ -967,6 +954,7 @@ test "peripheral register properties" { \\ \\ \\ TEST_REGISTER + \\ 0 \\ \\ \\ @@ -989,7 +977,7 @@ test "peripheral register properties" { try expectAttr(db, "reset_mask", 0xffff, register_id); } -test "register register properties" { +test "svd.register register properties" { const text = \\ \\ TEST_DEVICE @@ -1008,6 +996,7 @@ test "register register properties" { \\ \\ \\ TEST_REGISTER + \\ 0 \\ 8 \\ read-write \\ 0x0002 @@ -1034,7 +1023,7 @@ test "register register properties" { try expectAttr(db, "reset_mask", 0xff, register_id); } -test "register with fields" { +test "svd.register with fields" { const text = \\ \\ TEST_DEVICE @@ -1049,6 +1038,7 @@ test "register with fields" { \\ \\ \\ TEST_REGISTER + \\ 0 \\ \\ \\ TEST_FIELD @@ -1073,7 +1063,7 @@ test "register with fields" { try expectAttr(db, "access", .read_write, field_id); } -test "field with enum value" { +test "svd.field with enum value" { const text = \\ \\ TEST_DEVICE @@ -1088,6 +1078,7 @@ test "field with enum value" { \\ \\ \\ TEST_REGISTER + \\ 0 \\ \\ \\ TEST_FIELD @@ -1139,3 +1130,153 @@ test "field with enum value" { try expectAttr(db, "parent", enum_id, enum_field2_id); try expectAttr(db, "description", "test enum field 2", enum_field2_id); } + +test "svd.peripheral with dimElementGroup" { + const text = + \\ + \\ TEST_DEVICE + \\ 32 + \\ read-only + \\ 0x00000000 + \\ 0xffffffff + \\ + \\ + \\ TEST_PERIPHERAL + \\ 0x1000 + \\ 4 + \\ 4 + \\ + \\ + \\ TEST_REGISTER + \\ 0 + \\ + \\ + \\ + \\ + \\ + ; + + var doc = try xml.Doc.fromMemory(text); + var db = try Database.initFromSvd(std.testing.allocator, doc); + defer db.deinit(); + + const peripheral_id = try db.getEntityIdByName("type.peripheral", "TEST_PERIPHERAL"); + try expectAttr(db, "size", 4, peripheral_id); + + const instance_id = try db.getEntityIdByName("instance.peripheral", "TEST_PERIPHERAL"); + try expectAttr(db, "count", 4, instance_id); +} + +test "svd.peripheral with dimElementgroup, dimIndex set" { + const text = + \\ + \\ TEST_DEVICE + \\ 32 + \\ read-only + \\ 0x00000000 + \\ 0xffffffff + \\ + \\ + \\ TEST_PERIPHERAL + \\ 0x1000 + \\ 4 + \\ 4 + \\ foo + \\ + \\ + \\ TEST_REGISTER + \\ 0 + \\ + \\ + \\ + \\ + \\ + ; + + var doc = try xml.Doc.fromMemory(text); + var db = try Database.initFromSvd(std.testing.allocator, doc); + defer db.deinit(); + + _ = try db.getEntityIdByName("instance.device", "TEST_DEVICE"); + + // should not exist since dimIndex is not allowed to be defined for peripherals + try expectError(error.NameNotFound, db.getEntityIdByName("type.peripheral", "TEST_PERIPHERAL")); + try expectError(error.NameNotFound, db.getEntityIdByName("instance.peripheral", "TEST_PERIPHERAL")); +} + +test "svd.register with dimElementGroup" { + const text = + \\ + \\ TEST_DEVICE + \\ 32 + \\ read-only + \\ 0x00000000 + \\ 0xffffffff + \\ + \\ + \\ TEST_PERIPHERAL + \\ 0x1000 + \\ + \\ + \\ TEST_REGISTER + \\ 0 + \\ 4 + \\ 4 + \\ + \\ + \\ + \\ + \\ + ; + + var doc = try xml.Doc.fromMemory(text); + var db = try Database.initFromSvd(std.testing.allocator, doc); + defer db.deinit(); + + const register_id = try db.getEntityIdByName("type.register", "TEST_REGISTER"); + try expectAttr(db, "count", 4, register_id); +} + +test "svd.register with dimElementGroup, dimIncrement != size" { + const text = + \\ + \\ TEST_DEVICE + \\ 32 + \\ read-only + \\ 0x00000000 + \\ 0xffffffff + \\ + \\ + \\ TEST_PERIPHERAL + \\ 0x1000 + \\ + \\ + \\ TEST_REGISTER + \\ 0 + \\ 4 + \\ 8 + \\ + \\ + \\ TEST_FIELD + \\ read-write + \\ [7:0] + \\ + \\ + \\ + \\ + \\ + \\ + \\ + ; + + var doc = try xml.Doc.fromMemory(text); + var db = try Database.initFromSvd(std.testing.allocator, doc); + defer db.deinit(); + + _ = try db.getEntityIdByName("instance.device", "TEST_DEVICE"); + _ = try db.getEntityIdByName("instance.peripheral", "TEST_PERIPHERAL"); + _ = try db.getEntityIdByName("type.peripheral", "TEST_PERIPHERAL"); + + // dimIncrement is different than the size of the register, so it should never be made + try expectError(error.NameNotFound, db.getEntityIdByName("type.register", "TEST_REGISTER")); +}