From 4b7c5c531b0dd01b98f4de998b317a045497d7c6 Mon Sep 17 00:00:00 2001 From: Matt Knight Date: Sun, 13 Mar 2022 17:17:40 -0700 Subject: [PATCH] ATDF (#2) * snapshotting so that I can work on this as I travel * add atdf parsing * update readme * fix register generation edge case and skip fields named RESERVED --- tools/regz/README.md | 22 +- tools/regz/build.zig | 10 + tools/regz/libs/zig-clap | 2 +- tools/regz/src/Database.zig | 660 ++++++++++++++++++++++++--------- tools/regz/src/Enumeration.zig | 6 + tools/regz/src/Field.zig | 11 + tools/regz/src/Peripheral.zig | 4 + tools/regz/src/Register.zig | 4 + tools/regz/src/atdf.zig | 92 +++++ tools/regz/src/main.zig | 20 +- tools/regz/src/svd.zig | 214 +++++------ 11 files changed, 733 insertions(+), 312 deletions(-) create mode 100644 tools/regz/src/Enumeration.zig create mode 100644 tools/regz/src/Field.zig create mode 100644 tools/regz/src/Peripheral.zig create mode 100644 tools/regz/src/Register.zig create mode 100644 tools/regz/src/atdf.zig diff --git a/tools/regz/README.md b/tools/regz/README.md index bc699fe..9ae5ace 100644 --- a/tools/regz/README.md +++ b/tools/regz/README.md @@ -2,8 +2,8 @@ regz is a Zig code generator for microcontrollers. Vendors often publish files that have the details of special function registers, for ARM this is called a -"System View Description" (SVD), and this tool outputs a single file for you to -start interacting with the hardware: +"System View Description" (SVD), for AVR the format is called ATDF. This tool +outputs a single file for you to start interacting with the hardware: ```zig const regs = @import("nrf52.zig").registers; @@ -22,12 +22,15 @@ pub fn main() void { NOTE: just including that file is not enough to run code on a microcontroller, this is a fairly low-level tool and it is intended that the generated code be -used with [microzig](https://github.com/ZigEmbeddedGroup/microzig) +used with something like [microzig](https://github.com/ZigEmbeddedGroup/microzig). -One can get the required SVD file from your vendor, or another good place is +One can get SVD files from your vendor, or another good place is [posborne/cmsis-svd](https://github.com/posborne/cmsis-svd/tree/master/data), it's a python based SVD parser and they have a large number of files available. +For ATDF you need to unzip the appropriate atpack from the +[registry](https://packs.download.microchip.com). + ## Building regz targets zig master. @@ -39,6 +42,8 @@ zig build ## Using regz to generate code +Files provided may be either SVD or ATDF. + Provide path on command line: ``` regz > my-chip.zig @@ -56,13 +61,6 @@ RISC-V based products despite it being an ARM standard. At best regz will generate the register definitions without an interrupt table (for now), if you run into problems issues will be warmly welcomed! -### How about AVR? - -Atmel/Microchip publishes their register definitions for AVRs in ATDF, it is -not implemented, but we do plan on supporting it. There are tools like -[Rahix/atdf2svd](https://github.com/Rahix/atdf2svd) if you really can't wait to -get your hands dirty. - ### What about MSP430? TI does have another type of XML-based register schema, it is also @@ -82,5 +80,5 @@ future. If you know of any others we should look into, please make an issue! - [ ] finalize derivation of different components - [ ] comprehensive suite of tests - [ ] RISC-V interrupt table generation -- [ ] ATDF: Atmel's register schema format +- [x] ATDF: AVR's register schema format - [ ] insert name of Texus Insturment's register schema format for MSP430 diff --git a/tools/regz/build.zig b/tools/regz/build.zig index e122e39..0b88ec5 100644 --- a/tools/regz/build.zig +++ b/tools/regz/build.zig @@ -12,9 +12,19 @@ pub fn build(b: *std.build.Builder) !void { }); xml.step.install(); + const commit_result = try std.ChildProcess.exec(.{ + .allocator = b.allocator, + .argv = &.{ "git", "rev-parse", "HEAD" }, + .cwd = std.fs.path.dirname(@src().file) orelse unreachable, + }); + + const build_options = b.addOptions(); + build_options.addOption([]const u8, "commit", commit_result.stdout); + const exe = b.addExecutable("regz", "src/main.zig"); exe.setTarget(target); exe.setBuildMode(mode); + exe.addOptions("build_options", build_options); exe.addPackagePath("clap", "libs/zig-clap/clap.zig"); xml.link(exe); exe.install(); diff --git a/tools/regz/libs/zig-clap b/tools/regz/libs/zig-clap index cf8a34d..5f7b75d 160000 --- a/tools/regz/libs/zig-clap +++ b/tools/regz/libs/zig-clap @@ -1 +1 @@ -Subproject commit cf8a34d11f0520bdf2afc08eda88862597a88b23 +Subproject commit 5f7b75d5523d9581eca5a72a362868ff517992e8 diff --git a/tools/regz/src/Database.zig b/tools/regz/src/Database.zig index a93b105..2e949f3 100644 --- a/tools/regz/src/Database.zig +++ b/tools/regz/src/Database.zig @@ -1,6 +1,12 @@ const std = @import("std"); +const build_options = @import("build_options"); const svd = @import("svd.zig"); const xml = @import("xml.zig"); +const atdf = @import("atdf.zig"); +const Peripheral = @import("Peripheral.zig"); +const Register = @import("Register.zig"); +const Field = @import("Field.zig"); +const Enumeration = @import("Enumeration.zig"); const assert = std.debug.assert; const ArenaAllocator = std.heap.ArenaAllocator; @@ -38,58 +44,76 @@ const Nesting = enum { contained, }; -arena: std.heap.ArenaAllocator, -device: svd.Device, +allocator: Allocator, +arena: ArenaAllocator, +device: ?svd.Device, cpu: ?svd.Cpu, interrupts: std.ArrayList(svd.Interrupt), -peripherals: std.ArrayList(svd.Peripheral), +peripherals: std.ArrayList(Peripheral), clusters: std.ArrayList(svd.Cluster), -registers: std.ArrayList(svd.Register), -fields: std.ArrayList(svd.Field), +registers: std.ArrayList(Register), +fields: std.ArrayList(Field), +enumerations: std.ArrayList(Enumeration), peripherals_use_interrupts: std.ArrayList(PeripheralUsesInterrupt), clusters_in_peripherals: std.ArrayList(ClusterInPeripheral), clusters_in_clusters: std.ArrayList(ClusterInCluster), registers_in_peripherals: std.AutoHashMap(u32, Range), registers_in_clusters: std.AutoHashMap(u32, Range), -fields_in_registers: std.MultiArrayList(FieldsInRegister), +fields_in_registers: std.AutoHashMap(u32, Range), +enumerations_in_fields: std.AutoHashMap(u32, Range), dimensions: Dimensions, /// takes ownership of arena allocator -fn init(arena: std.heap.ArenaAllocator, device: svd.Device) Self { - const allocator = arena.child_allocator; +fn init(allocator: Allocator) Self { return Self{ - .arena = arena, - .device = device, + .allocator = allocator, + .arena = ArenaAllocator.init(allocator), + .device = null, .cpu = null, .interrupts = std.ArrayList(svd.Interrupt).init(allocator), - .peripherals = std.ArrayList(svd.Peripheral).init(allocator), + .peripherals = std.ArrayList(Peripheral).init(allocator), .clusters = std.ArrayList(svd.Cluster).init(allocator), - .registers = std.ArrayList(svd.Register).init(allocator), - .fields = std.ArrayList(svd.Field).init(allocator), + .registers = std.ArrayList(Register).init(allocator), + .fields = std.ArrayList(Field).init(allocator), + .enumerations = std.ArrayList(Enumeration).init(allocator), .peripherals_use_interrupts = std.ArrayList(PeripheralUsesInterrupt).init(allocator), .clusters_in_peripherals = std.ArrayList(ClusterInPeripheral).init(allocator), .clusters_in_clusters = std.ArrayList(ClusterInCluster).init(allocator), .registers_in_peripherals = std.AutoHashMap(u32, Range).init(allocator), .registers_in_clusters = std.AutoHashMap(u32, Range).init(allocator), - .fields_in_registers = std.MultiArrayList(FieldsInRegister){}, + .fields_in_registers = std.AutoHashMap(u32, Range).init(allocator), + .enumerations_in_fields = std.AutoHashMap(u32, Range).init(allocator), .dimensions = Dimensions.init(allocator), }; } -pub fn initFromSvd(allocator: std.mem.Allocator, doc: *xml.Doc) !Self { +pub fn deinit(self: *Self) void { + self.interrupts.deinit(); + self.peripherals.deinit(); + self.clusters.deinit(); + self.registers.deinit(); + self.fields.deinit(); + self.enumerations.deinit(); + self.peripherals_use_interrupts.deinit(); + self.registers_in_peripherals.deinit(); + self.fields_in_registers.deinit(); + self.clusters_in_peripherals.deinit(); + self.clusters_in_clusters.deinit(); + self.registers_in_clusters.deinit(); + self.enumerations_in_fields.deinit(); + self.dimensions.deinit(); + self.arena.deinit(); +} + +pub fn initFromSvd(allocator: Allocator, doc: *xml.Doc) !Self { const root_element: *xml.Node = xml.docGetRootElement(doc) orelse return error.NoRoot; const device_node = xml.findNode(root_element, "device") orelse return error.NoDevice; const device_nodes: *xml.Node = device_node.children orelse return error.NoDeviceNodes; - var arena = std.heap.ArenaAllocator.init(allocator); - const device = blk: { - errdefer arena.deinit(); - break :blk try svd.Device.parse(&arena, device_nodes); - }; - - var db = Self.init(arena, device); + var db = Self.init(allocator); errdefer db.deinit(); + db.device = try svd.Device.parse(&db.arena, device_nodes); db.cpu = if (xml.findNode(device_nodes, "cpu")) |cpu_node| try svd.Cpu.parse(&db.arena, @ptrCast(*xml.Node, cpu_node.children orelse return error.NoCpu)) else @@ -102,7 +126,7 @@ pub fn initFromSvd(allocator: std.mem.Allocator, doc: *xml.Doc) !Self { var peripheral_it: ?*xml.Node = xml.findNode(peripherals_node.children, "peripheral"); //peripherals_node.children; while (peripheral_it != null) : (peripheral_it = xml.findNode(peripheral_it.?.next, "peripheral")) { const peripheral_nodes: *xml.Node = peripheral_it.?.children orelse continue; - const peripheral = try svd.Peripheral.parse(&db.arena, peripheral_nodes); + const peripheral = try svd.parsePeripheral(&db.arena, peripheral_nodes); try db.peripherals.append(peripheral); const peripheral_idx = @intCast(u32, db.peripherals.items.len - 1); @@ -231,11 +255,17 @@ pub fn initFromSvd(allocator: std.mem.Allocator, doc: *xml.Doc) !Self { const parent_idx = entry.value_ptr.*; const child_idx = entry.key_ptr.*; - if (db.fields_in_registers.items(.field_range)[child_idx].begin != - db.fields_in_registers.items(.field_range)[child_idx].end) + // TODO: determine how deriving from a register while having fields + // works, does it just merge the fields in? + if (db.fields_in_registers.contains(child_idx)) return error.Todo; - db.fields_in_registers.items(.field_range)[child_idx] = db.fields_in_registers.items(.field_range)[parent_idx]; + if (db.fields_in_registers.get(parent_idx)) |field_range| + try db.fields_in_registers.put(child_idx, field_range) + else + std.log.warn("register '{s}' derived from '{s}', but the latter does not have fields", .{ + db.registers.items[child_idx].name, db.registers.items[parent_idx].name, + }); } } @@ -250,22 +280,26 @@ fn loadRegisters( var register_it: ?*xml.Node = xml.findNode(nodes, "register"); while (register_it != null) : (register_it = xml.findNode(register_it.?.next, "register")) { const register_nodes: *xml.Node = register_it.?.children orelse continue; - const register = try svd.Register.parse(&db.arena, register_nodes, db.device.register_properties.size orelse db.device.width); + const register = try svd.parseRegister(&db.arena, register_nodes, db.device.?.register_properties.size orelse db.device.?.width); + const register_idx = @intCast(u32, db.registers.items.len); try db.registers.append(register); - const register_idx = @intCast(u32, db.registers.items.len - 1); if (xml.getAttribute(register_it, "derivedFrom")) |derived_from| try named_derivations.registers.put(register_idx, try db.arena.allocator().dupe(u8, derived_from)); if (try svd.Dimension.parse(&db.arena, register_nodes)) |dimension| try db.dimensions.registers.put(register_idx, dimension); - const field_begin_idx = db.fields.items.len; + const field_begin_idx = @intCast(u32, db.fields.items.len); if (xml.findNode(register_nodes, "fields")) |fields_node| { var field_it: ?*xml.Node = xml.findNode(fields_node.children, "field"); while (field_it != null) : (field_it = xml.findNode(field_it.?.next, "field")) { const field_nodes: *xml.Node = field_it.?.children orelse continue; - const field = try svd.Field.parse(&db.arena, field_nodes); + const field_name = xml.findValueForKey(field_nodes, "name") orelse continue; + if (useless_field_names.has(field_name)) + continue; + + const field = try svd.parseField(&db.arena, field_nodes); try db.fields.append(field); const field_idx = @intCast(u32, db.fields.items.len - 1); @@ -292,7 +326,7 @@ fn loadRegisters( } // sort fields by offset - std.sort.sort(svd.Field, db.fields.items[field_begin_idx..], {}, svd.Field.lessThan); + std.sort.sort(Field, db.fields.items[field_begin_idx..], {}, Field.lessThan); // TODO: can we use unions for overlapping fields? // remove overlapping fields @@ -310,14 +344,14 @@ fn loadRegisters( db.fields.items[i - 1].offset + db.fields.items[i - 1].width, register.name, }); - } else if (db.fields.items[i].offset + db.fields.items[i].width > db.device.width) { + } else if (db.fields.items[i].offset + db.fields.items[i].width > db.device.?.width) { const ignored = db.fields.orderedRemove(i); std.log.warn("ignoring field '{s}' ({}-{}) in register '{s}' because it's outside it's size: {}", .{ ignored.name, ignored.offset, ignored.offset + ignored.width, register.name, - db.device.width, + db.device.?.width, }); } else { current_bit = db.fields.items[i].offset + db.fields.items[i].width; @@ -325,13 +359,11 @@ fn loadRegisters( } } - try db.fields_in_registers.append(db.arena.child_allocator, .{ - .register_idx = @intCast(u32, db.registers.items.len - 1), - .field_range = .{ - .begin = @intCast(u32, field_begin_idx), + if (field_begin_idx != db.fields.items.len) + try db.fields_in_registers.put(register_idx, .{ + .begin = field_begin_idx, .end = @intCast(u32, db.fields.items.len), - }, - }); + }); } } @@ -372,27 +404,238 @@ fn loadNestedClusters( } } -pub fn initFromAtdf(allocator: std.mem.Allocator, doc: *xml.Doc) !Self { - _ = doc; - _ = allocator; - return error.Todo; -} +pub fn initFromAtdf(allocator: Allocator, doc: *xml.Doc) !Self { + const root_element: *xml.Node = xml.docGetRootElement(doc) orelse return error.NoRoot; + const tools_node = xml.findNode(root_element, "avr-tools-device-file") orelse return error.NoToolsNode; + var regs_start_addr: usize = 0; -pub fn deinit(self: *Self) void { - const allocator = self.arena.child_allocator; - self.peripherals.deinit(); - self.interrupts.deinit(); - self.registers.deinit(); - self.fields.deinit(); - self.clusters.deinit(); - self.peripherals_use_interrupts.deinit(); - self.registers_in_peripherals.deinit(); - self.fields_in_registers.deinit(allocator); - self.clusters_in_peripherals.deinit(); - self.clusters_in_clusters.deinit(); - self.registers_in_clusters.deinit(); - self.dimensions.deinit(); - self.arena.deinit(); + var peripheral_instances = std.StringHashMap(void).init(allocator); + defer peripheral_instances.deinit(); + + var db = Self.init(allocator); + errdefer db.deinit(); + + if (xml.findNode(tools_node.children orelse return error.NoChildren, "devices")) |devices_node| { + var device_it: ?*xml.Node = xml.findNode(devices_node.children, "device"); + while (device_it != null) : (device_it = xml.findNode(device_it.?.next, "device")) { + if (db.device != null) { + std.log.err("multiple devices defined in this file. TODO: give user list of devices to choose from", .{}); + return error.Explained; + } + + // this name lowercased should line up with a zig target + const name: ?[]const u8 = if (xml.getAttribute(device_it, "name")) |n| + try db.arena.allocator().dupe(u8, n) + else + null; + const arch: ?[]const u8 = if (xml.getAttribute(device_it, "architecture")) |a| + try db.arena.allocator().dupe(u8, a) + else + null; + const family: ?[]const u8 = if (xml.getAttribute(device_it, "family")) |f| + try db.arena.allocator().dupe(u8, f) + else + null; + + db.device = .{ + .vendor = "Atmel", + .series = family, + .name = name, + .address_unit_bits = 16, + .width = 8, + .register_properties = .{}, + }; + + db.cpu = svd.Cpu{ + .name = arch, + .revision = "0.0.1", + .endian = .little, + .nvic_prio_bits = 0, + .vendor_systick_config = false, + .device_num_interrupts = null, + }; + + const device_nodes: *xml.Node = device_it.?.children orelse continue; + regs_start_addr = if (xml.findNode(device_nodes, "address-spaces")) |address_spaces_node| blk: { + var ret: ?usize = null; + var address_space_it: ?*xml.Node = xml.findNode(address_spaces_node.children.?, "address-space"); + while (address_space_it != null) : (address_space_it = xml.findNode(address_space_it.?.next, "address-space")) { + const address_space_nodes: *xml.Node = address_space_it.?.children orelse continue; + var memory_segment_it: ?*xml.Node = xml.findNode(address_space_nodes, "memory-segment"); + while (memory_segment_it != null) : (memory_segment_it = xml.findNode(memory_segment_it.?.next, "memory-segment")) { + const memory_type = xml.getAttribute(memory_segment_it, "type") orelse continue; + if (std.mem.eql(u8, "regs", memory_type)) { + if (ret != null) { + std.log.err("multiple register memory segments found, no idea what to do, please cut a ticket: https://github.com/ZigEmbeddedGroup/regz", .{}); + return error.Explained; + } else if (xml.getAttribute(memory_segment_it, "start")) |addr_str| { + ret = try std.fmt.parseInt(usize, addr_str, 0); + } + } + } + } + + if (ret) |start_address| { + break :blk start_address; + } else { + std.log.err("failed to determine the start address for registers", .{}); + return error.Explained; + } + } else unreachable; + + if (xml.findNode(device_nodes, "peripherals")) |peripherals_node| { + var module_it: ?*xml.Node = xml.findNode(peripherals_node.children.?, "module"); + while (module_it != null) : (module_it = xml.findNode(module_it.?.next, "module")) { + const module_nodes = module_it.?.children orelse continue; + var instance_it: ?*xml.Node = xml.findNode(module_nodes, "instance"); + while (instance_it != null) : (instance_it = xml.findNode(instance_it.?.next, "instance")) { + const instance_nodes = instance_it.?.children orelse continue; + const instance_name = xml.getAttribute(instance_it, "name") orelse return error.NoInstanceName; + if (xml.findNode(instance_nodes, "register-group")) |register_group| { + const group_name = xml.getAttribute(register_group, "name") orelse return error.NoRegisterGroupName; + const name_in_module = xml.getAttribute(register_group, "name-in-module") orelse return error.NoNameInModule; + + if (!std.mem.eql(u8, instance_name, group_name) or !std.mem.eql(u8, group_name, name_in_module)) { + std.log.warn("mismatching names for name-in-module: {s}, ignoring, if you see this please cut a ticket: https://github.com/ZigEmbeddedGroup/regz", .{ + name_in_module, + }); + continue; + } + + try peripheral_instances.put(try db.arena.allocator().dupe(u8, name_in_module), {}); + } + } + } + } + + if (xml.findNode(device_nodes, "interrupts")) |interrupts_node| { + var interrupt_it: ?*xml.Node = xml.findNode(interrupts_node.children.?, "interrupt"); + while (interrupt_it != null) : (interrupt_it = xml.findNode(interrupt_it.?.next, "interrupt")) { + const interrupt = svd.Interrupt{ + .name = if (xml.getAttribute(interrupt_it, "name")) |interrupt_name| + try db.arena.allocator().dupe(u8, interrupt_name) + else + return error.NoName, + .description = if (xml.getAttribute(interrupt_it, "caption")) |caption| + try db.arena.allocator().dupe(u8, caption) + else + return error.NoCaption, + .value = if (xml.getAttribute(interrupt_it, "index")) |index_str| + try std.fmt.parseInt(usize, index_str, 0) + else + return error.NoIndex, + }; + + // if the interrupt doesn't exist then do a sorted insert + if (std.sort.binarySearch(svd.Interrupt, interrupt, db.interrupts.items, {}, svd.Interrupt.compare) == null) { + try db.interrupts.append(interrupt); + std.sort.sort(svd.Interrupt, db.interrupts.items, {}, svd.Interrupt.lessThan); + } + } + } + } + } + + if (xml.findNode(tools_node.children orelse return error.NoChildren, "modules")) |modules_node| { + var module_it: ?*xml.Node = xml.findNode(modules_node.children.?, "module"); + while (module_it != null) : (module_it = xml.findNode(module_it.?.next, "module")) { + const module_nodes: *xml.Node = module_it.?.children orelse continue; + + var value_groups = std.StringHashMap(Range).init(allocator); + defer value_groups.deinit(); + + var value_group_it: ?*xml.Node = xml.findNode(module_nodes, "value-group"); + while (value_group_it != null) : (value_group_it = xml.findNode(value_group_it.?.next, "value-group")) { + const value_group_nodes: *xml.Node = value_group_it.?.children orelse continue; + const value_group_name = if (xml.getAttribute(value_group_it, "name")) |name| + try db.arena.allocator().dupe(u8, name) + else + continue; + + const first_enum_idx = @intCast(u32, db.enumerations.items.len); + var value_it: ?*xml.Node = xml.findNode(value_group_nodes, "value"); + while (value_it != null) : (value_it = xml.findNode(value_it.?.next, "value")) { + try db.enumerations.append(.{ + .value = if (xml.getAttribute(value_it, "value")) |value_str| + try std.fmt.parseInt(usize, value_str, 0) + else + continue, + .description = if (xml.getAttribute(value_it, "caption")) |caption| + try db.arena.allocator().dupe(u8, caption) + else + null, + }); + } + + std.sort.sort(Enumeration, db.enumerations.items[first_enum_idx..], {}, Enumeration.lessThan); + try value_groups.put(value_group_name, .{ + .begin = first_enum_idx, + .end = @intCast(u32, db.enumerations.items.len), + }); + } + + var register_group_it: ?*xml.Node = xml.findNode(module_nodes, "register-group"); + while (register_group_it != null) : (register_group_it = xml.findNode(register_group_it.?.next, "register-group")) { + const register_group_nodes: *xml.Node = register_group_it.?.children orelse continue; + const group_name = xml.getAttribute(register_group_it, "name") orelse continue; + if (!peripheral_instances.contains(group_name)) + continue; + + const peripheral_idx = @intCast(u32, db.peripherals.items.len); + try db.peripherals.append(try atdf.parsePeripheral(&db.arena, register_group_it.?)); + + const reg_begin_idx = @intCast(u32, db.registers.items.len); + var register_it: ?*xml.Node = xml.findNode(register_group_nodes, "register"); + while (register_it != null) : (register_it = xml.findNode(register_it.?.next, "register")) { + const register_idx = @intCast(u32, db.registers.items.len); + try db.registers.append(try atdf.parseRegister(&db.arena, register_it.?, regs_start_addr, register_it.?.children != null)); + + const register_nodes: *xml.Node = register_it.?.children orelse continue; + const field_begin_idx = @intCast(u32, db.fields.items.len); + var bitfield_it: ?*xml.Node = xml.findNode(register_nodes, "bitfield"); + while (bitfield_it != null) : (bitfield_it = xml.findNode(bitfield_it.?.next, "bitfield")) { + try db.fields.append(atdf.parseField(&db.arena, bitfield_it.?) catch |err| switch (err) { + error.InvalidMask => continue, + else => return err, + }); + } + + // we expect fields to be sorted by offset + std.sort.sort(Field, db.fields.items[field_begin_idx..], {}, Field.lessThan); + + // go back through bitfields and get the enumerations + bitfield_it = xml.findNode(register_nodes, "bitfield"); + while (bitfield_it != null) : (bitfield_it = xml.findNode(bitfield_it.?.next, "bitfield")) { + if (xml.getAttribute(bitfield_it, "values")) |value_group_name| { + const field_name = xml.getAttribute(bitfield_it, "name") orelse continue; + const field_idx = for (db.fields.items[field_begin_idx..]) |field, offset| { + if (std.mem.eql(u8, field_name, field.name)) + break field_begin_idx + @intCast(u32, offset); + } else continue; + + if (value_groups.get(value_group_name)) |enum_range| + try db.enumerations_in_fields.put(field_idx, enum_range); + } + } + + try db.fields_in_registers.put(register_idx, .{ + .begin = field_begin_idx, + .end = @intCast(u32, db.fields.items.len), + }); + } + + try db.registers_in_peripherals.put(peripheral_idx, .{ + .begin = reg_begin_idx, + .end = @intCast(u32, db.registers.items.len), + }); + } + } + } + + // there is also pinouts, however that's linked to IC package information, + // not exactly sure if we're going to do anything with that + + return db; } fn writeDescription( @@ -436,11 +679,20 @@ fn writeDescription( } pub fn toZig(self: *Self, writer: anytype) !void { - try writer.writeAll("// this file is generated by regz\n//\n"); - if (self.device.vendor) |vendor_name| + if (self.device == null) { + std.log.err("failed to find device info", .{}); + return error.Explained; + } + + try writer.print( + \\// this file was generated by regz: https://github.com/ZigEmbeddedGroup/regz + \\// commit: {s} + , .{build_options.commit}); + try writer.writeAll("//\n"); + if (self.device.?.vendor) |vendor_name| try writer.print("// vendor: {s}\n", .{vendor_name}); - if (self.device.name) |device_name| + if (self.device.?.name) |device_name| try writer.print("// device: {s}\n", .{device_name}); if (self.cpu) |cpu| if (cpu.name) |cpu_name| @@ -450,43 +702,46 @@ pub fn toZig(self: *Self, writer: anytype) !void { if (svd.CpuName.parse(self.cpu.?.name.?)) |cpu_type| { try writer.writeAll("\npub const VectorTable = struct {\n"); - // this is an arm machine - try writer.writeAll( - \\ initial_stack_pointer: u32, - \\ Reset: InterruptVector = unhandled, - \\ NMI: InterruptVector = unhandled, - \\ HardFault: InterruptVector = unhandled, - \\ - ); - - switch (cpu_type) { - // Cortex M23 has a security extension and when implemented - // there are two vector tables (same layout though) - .cortex_m0, .cortex_m0plus, .cortex_m23 => try writer.writeAll( - \\ reserved0: [7]u32 = undefined, + // TODO: isCortexM() + if (cpu_type != .avr) { + // this is an arm machine + try writer.writeAll( + \\ initial_stack_pointer: u32, + \\ Reset: InterruptVector = unhandled, + \\ NMI: InterruptVector = unhandled, + \\ HardFault: InterruptVector = unhandled, \\ - ), - .sc300, .cortex_m3, .cortex_m4, .cortex_m7, .cortex_m33 => try writer.writeAll( - \\ MemManage: InterruptVector = unhandled, - \\ BusFault: InterruptVector = unhandled, - \\ UsageFault: InterruptVector = unhandled, - \\ reserved0: [4]u32 = undefined, + ); + + switch (cpu_type) { + // Cortex M23 has a security extension and when implemented + // there are two vector tables (same layout though) + .cortex_m0, .cortex_m0plus, .cortex_m23 => try writer.writeAll( + \\ reserved0: [7]u32 = undefined, + \\ + ), + .sc300, .cortex_m3, .cortex_m4, .cortex_m7, .cortex_m33 => try writer.writeAll( + \\ MemManage: InterruptVector = unhandled, + \\ BusFault: InterruptVector = unhandled, + \\ UsageFault: InterruptVector = unhandled, + \\ reserved0: [4]u32 = undefined, + \\ + ), + else => { + std.log.err("unhandled cpu type: {}", .{cpu_type}); + return error.Todo; + }, + } + + try writer.writeAll( + \\ SVCall: InterruptVector = unhandled, + \\ reserved1: [2]u32 = undefined, + \\ PendSV: InterruptVector = unhandled, + \\ SysTick: InterruptVector = unhandled, \\ - ), - else => { - std.log.err("unhandled cpu type: {}", .{cpu_type}); - return error.Todo; - }, + ); } - try writer.writeAll( - \\ SVCall: InterruptVector = unhandled, - \\ reserved1: [2]u32 = undefined, - \\ PendSV: InterruptVector = unhandled, - \\ SysTick: InterruptVector = unhandled, - \\ - ); - var reserved_count: usize = 2; var expected: usize = 0; for (self.interrupts.items) |interrupt| { @@ -502,7 +757,7 @@ pub fn toZig(self: *Self, writer: anytype) !void { try writer.print(" reserved{}: u32 = undefined,\n", .{reserved_count}); } - if (interrupt.description) |description| if (!isUselessDescription(description)) + if (interrupt.description) |description| if (!useless_descriptions.has(description)) try writeDescription(self.arena.child_allocator, writer, description, 1); try writer.print(" {s}: InterruptVector = unhandled,\n", .{std.zig.fmtId(interrupt.name)}); @@ -534,13 +789,15 @@ pub fn toZig(self: *Self, writer: anytype) !void { const reg_range = self.registers_in_peripherals.get(peripheral_idx).?; const registers = self.registers.items[reg_range.begin..reg_range.end]; if (registers.len != 0 or has_clusters) { - if (peripheral.description) |description| if (!isUselessDescription(description)) + if (peripheral.description) |description| if (!useless_descriptions.has(description)) { + try writer.writeByte('\n'); try writeDescription(self.arena.child_allocator, writer, description, 1); - try writer.print( - \\ pub const {s} = struct {{ - \\ pub const base_address = 0x{x}; - \\ - , .{ std.zig.fmtId(peripheral.name), peripheral.base_addr }); + }; + + try writer.print(" pub const {s} = struct {{\n", .{std.zig.fmtId(peripheral.name)}); + if (peripheral.base_addr) |base_addr| + try writer.print(" pub const base_address = 0x{x};\n", .{base_addr}); + if (peripheral.version) |version| try writer.print(" pub const version = \"{s}\";\n", .{version}); @@ -575,7 +832,7 @@ pub fn toZig(self: *Self, writer: anytype) !void { fn genZigCluster( db: *Self, writer: anytype, - base_addr: usize, + base_addr: ?usize, cluster_idx: u32, indent: usize, nesting: Nesting, @@ -595,7 +852,7 @@ fn genZigCluster( const registers = db.registers.items[range.begin..range.end]; try writer.writeByte('\n'); if (cluster.description) |description| - if (!isUselessDescription(description)) + if (!useless_descriptions.has(description)) try writeDescription(db.arena.child_allocator, writer, description, indent); if (dimension_opt) |dimension| { @@ -612,11 +869,11 @@ fn genZigCluster( bits += register.size; } - if (bits % 8 != 0 or db.device.width % 8 != 0) + if (bits % 8 != 0 or db.device.?.width % 8 != 0) return error.InvalidWordSize; const bytes = bits / 8; - const bytes_per_word = db.device.width / 8; + const bytes_per_word = db.device.?.width / 8; if (bytes > dimension.increment) return error.InvalidClusterSize; @@ -624,7 +881,7 @@ fn genZigCluster( var i: usize = 0; while (i < num_padding_words) : (i += 1) { try writer.writeByteNTimes(' ', (indent + 1) * 4); - try writer.print("padding{}: u{},\n", .{ i, db.device.width }); + try writer.print("padding{}: u{},\n", .{ i, db.device.?.width }); } try writer.writeByteNTimes(' ', indent * 4); @@ -650,25 +907,62 @@ fn genZigSingleRegister( writer: anytype, name: []const u8, width: usize, + has_base_addr: bool, addr_offset: usize, - fields: []svd.Field, - first_field_idx: u32, + field_range_opt: ?Range, array_prefix: []const u8, indent: usize, nesting: Nesting, ) !void { - const single_line_declaration = fields.len == 0 or (fields.len == 1 and std.mem.eql(u8, fields[0].name, name)); - if (single_line_declaration) { - if (fields.len == 1 and fields[0].width < width) { + if (field_range_opt) |field_range| { + const fields = self.fields.items[field_range.begin..field_range.end]; + assert(fields.len != 0); + + if (fields.len == 1 and std.mem.eql(u8, fields[0].name, name)) { + if (fields[0].width > width) + return error.BadWidth; + try writer.writeByteNTimes(' ', indent * 4); - switch (nesting) { - .namespaced => try writer.print("pub const {s} = @intToPtr(*volatile {s}MmioInt({}, u{}), base_address + 0x{x});\n", .{ - std.zig.fmtId(name), - array_prefix, - width, - fields[0].width, - addr_offset, - }), + if (fields[0].width == width) + // TODO: oof please refactor this + switch (nesting) { + .namespaced => if (has_base_addr) + try writer.print("pub const {s} = @intToPtr(*volatile {s}u{}, base_address + 0x{x});\n", .{ + std.zig.fmtId(name), + array_prefix, + width, + addr_offset, + }) + else + try writer.print("pub const {s} = @intToPtr(*volatile {s}u{}, 0x{x});\n", .{ + std.zig.fmtId(name), + array_prefix, + width, + addr_offset, + }), + .contained => try writer.print("{s}: {s}u{},\n", .{ + std.zig.fmtId(name), + array_prefix, + width, + }), + } + else switch (nesting) { + .namespaced => if (has_base_addr) + try writer.print("pub const {s} = @intToPtr(*volatile {s}MmioInt({}, u{}), base_address + 0x{x});\n", .{ + std.zig.fmtId(name), + array_prefix, + width, + fields[0].width, + addr_offset, + }) + else + try writer.print("pub const {s} = @intToPtr(*volatile {s}MmioInt({}, u{}), 0x{x});\n", .{ + std.zig.fmtId(name), + array_prefix, + width, + fields[0].width, + addr_offset, + }), .contained => try writer.print("{s}: {s}MmioInt({}, u{}),\n", .{ std.zig.fmtId(name), array_prefix, @@ -676,52 +970,61 @@ fn genZigSingleRegister( fields[0].width, }), } - } else if (fields.len == 1 and fields[0].width > width) { - return error.BadWidth; } else { try writer.writeByteNTimes(' ', indent * 4); switch (nesting) { - .namespaced => try writer.print("pub const {s} = @intToPtr(*volatile {s}u{}, base_address + 0x{x});\n", .{ + .namespaced => try writer.print("pub const {s} = @intToPtr(*volatile {s}Mmio({}, packed struct {{\n", .{ std.zig.fmtId(name), array_prefix, width, - addr_offset, }), - .contained => try writer.print("{s}: {s}u{},\n", .{ + .contained => try writer.print("{s}: {s}Mmio({}, packed struct {{\n", .{ std.zig.fmtId(name), array_prefix, width, }), } + + try self.genZigFields( + writer, + width, + fields, + field_range.begin, + indent + 1, + ); + + try writer.writeByteNTimes(' ', indent * 4); + switch (nesting) { + .namespaced => if (has_base_addr) + try writer.print("}}), base_address + 0x{x});\n", .{addr_offset}) + else + try writer.print("}}), 0x{x});\n", .{addr_offset}), + .contained => try writer.writeAll("}),\n"), + } } } else { try writer.writeByteNTimes(' ', indent * 4); switch (nesting) { - .namespaced => try writer.print("pub const {s} = @intToPtr(*volatile {s}Mmio({}, packed struct{{\n", .{ - std.zig.fmtId(name), - array_prefix, - width, - }), - .contained => try writer.print("{s}: {s}Mmio({}, packed struct{{\n", .{ + .namespaced => if (has_base_addr) + try writer.print("pub const {s} = @intToPtr(*volatile {s}u{}, base_address + 0x{x});\n", .{ + std.zig.fmtId(name), + array_prefix, + width, + addr_offset, + }) + else + try writer.print("pub const {s} = @intToPtr(*volatile {s}u{}, 0x{x});\n", .{ + std.zig.fmtId(name), + array_prefix, + width, + addr_offset, + }), + .contained => try writer.print("{s}: {s}u{},\n", .{ std.zig.fmtId(name), array_prefix, width, }), } - - try self.genZigFields( - writer, - width, - fields, - first_field_idx, - indent + 1, - ); - - try writer.writeByteNTimes(' ', indent * 4); - switch (nesting) { - .namespaced => try writer.print("}}), base_address + 0x{x});\n", .{addr_offset}), - .contained => try writer.writeAll("}),\n"), - } } } @@ -729,7 +1032,7 @@ fn genZigFields( self: *Self, writer: anytype, reg_width: usize, - fields: []svd.Field, + fields: []Field, first_field_idx: u32, indent: usize, ) !void { @@ -798,10 +1101,29 @@ fn genZigFields( break; } + const enumerations_opt = if (self.enumerations_in_fields.get(field_idx)) |enum_range| + self.enumerations.items[enum_range.begin..enum_range.end] + else + null; + // TODO: default values? if (field.description) |description| - if (!isUselessDescription(description)) + if (!useless_descriptions.has(description)) { try writeDescription(self.arena.child_allocator, writer, description, indent); + if (enumerations_opt != null) { + try writer.writeByteNTimes(' ', indent * 4); + try writer.writeAll("///\n"); + } + }; + + if (enumerations_opt) |enumerations| for (enumerations) |enumeration| { + try writer.writeByteNTimes(' ', indent * 4); + try writer.print("/// 0x{x}: ", .{enumeration.value}); + if (enumeration.description) |description| + try writer.print("{s}\n", .{description}) + else + try writer.writeAll("undocumented\n"); + }; try writer.writeByteNTimes(' ', indent * 4); try writer.print("{s}: u{},\n", .{ std.zig.fmtId(field.name), field.width }); @@ -823,16 +1145,13 @@ fn genZigFields( fn genZigRegister( self: *Self, writer: anytype, - base_addr: usize, + base_addr: ?usize, reg_idx: u32, indent: usize, nesting: Nesting, ) !void { const register = self.registers.items[reg_idx]; - const fields = blk: { - const range = self.fields_in_registers.items(.field_range)[reg_idx]; - break :blk self.fields.items[range.begin..range.end]; - }; + const field_range = if (self.fields_in_registers.get(reg_idx)) |range| range else null; const dimension_opt = self.dimensions.registers.get(reg_idx); if (dimension_opt == null and std.mem.indexOf(u8, register.name, "%s") != null) @@ -858,8 +1177,9 @@ fn genZigRegister( try writer.writeByte('\n'); if (nesting == .namespaced) { + const addr = if (base_addr) |base| base + addr_offset else addr_offset; try writer.writeByteNTimes(' ', indent * 4); - try writer.print("/// address: 0x{x}\n", .{base_addr + addr_offset}); + try writer.print("/// address: 0x{x}\n", .{addr}); } if (register.description) |description| @@ -869,9 +1189,9 @@ fn genZigRegister( writer, name, register.size, + base_addr != null, addr_offset, - fields, - self.fields_in_registers.items(.field_range)[reg_idx].begin, + field_range, "", indent, nesting, @@ -893,8 +1213,9 @@ fn genZigRegister( try writer.writeByte('\n'); if (nesting == .namespaced) { + const addr = if (base_addr) |base| base + addr_offset else addr_offset; try writer.writeByteNTimes(' ', indent * 4); - try writer.print("/// address: 0x{x}\n", .{base_addr + addr_offset}); + try writer.print("/// address: 0x{x}\n", .{addr}); } if (register.description) |description| @@ -904,9 +1225,9 @@ fn genZigRegister( writer, name, register.size, + base_addr != null, addr_offset, - fields, - self.fields_in_registers.items(.field_range)[reg_idx].begin, + field_range, "", indent, nesting, @@ -949,8 +1270,9 @@ fn genZigRegister( const name = try std.mem.replaceOwned(u8, self.arena.allocator(), register.name, "[%s]", ""); try writer.writeByte('\n'); if (nesting == .namespaced) { + const addr = if (base_addr) |base| base + register.addr_offset else register.addr_offset; try writer.writeByteNTimes(' ', indent * 4); - try writer.print("/// address: 0x{x}\n", .{base_addr + register.addr_offset}); + try writer.print("/// address: 0x{x}\n", .{addr}); } if (register.description) |description| @@ -960,9 +1282,9 @@ fn genZigRegister( writer, name, register.size, + base_addr != null, register.addr_offset, - fields, - self.fields_in_registers.items(.field_range)[reg_idx].begin, + field_range, array_prefix, indent, nesting, @@ -978,7 +1300,6 @@ fn genZigRegister( std.log.info(" dim_index: {}", .{dimension.index}); } - std.log.info(" fields: {}", .{fields.len}); assert(false); // haven't figured out this configuration yet } @@ -1038,13 +1359,10 @@ const Dimensions = struct { } }; -const useless_descriptions: []const []const u8 = &.{ - "Unspecified", -}; +const useless_descriptions = std.ComptimeStringMap(void, .{ + .{"Unspecified"}, +}); -fn isUselessDescription(description: []const u8) bool { - return for (useless_descriptions) |useless_description| { - if (std.mem.eql(u8, description, useless_description)) - break true; - } else false; -} +const useless_field_names = std.ComptimeStringMap(void, .{ + .{"RESERVED"}, +}); diff --git a/tools/regz/src/Enumeration.zig b/tools/regz/src/Enumeration.zig new file mode 100644 index 0000000..81c3bd5 --- /dev/null +++ b/tools/regz/src/Enumeration.zig @@ -0,0 +1,6 @@ +value: usize, +description: ?[]const u8, + +pub fn lessThan(_: void, lhs: @This(), rhs: @This()) bool { + return lhs.value < rhs.value; +} diff --git a/tools/regz/src/Field.zig b/tools/regz/src/Field.zig new file mode 100644 index 0000000..b298ba2 --- /dev/null +++ b/tools/regz/src/Field.zig @@ -0,0 +1,11 @@ +name: []const u8, +description: ?[]const u8, +offset: u8, +width: u8, + +pub fn lessThan(_: void, lhs: @This(), rhs: @This()) bool { + return if (lhs.offset == rhs.offset) + lhs.width < rhs.width + else + lhs.offset < rhs.offset; +} diff --git a/tools/regz/src/Peripheral.zig b/tools/regz/src/Peripheral.zig new file mode 100644 index 0000000..e8fd1db --- /dev/null +++ b/tools/regz/src/Peripheral.zig @@ -0,0 +1,4 @@ +name: []const u8, +version: ?[]const u8, +description: ?[]const u8, +base_addr: ?usize, diff --git a/tools/regz/src/Register.zig b/tools/regz/src/Register.zig new file mode 100644 index 0000000..742335e --- /dev/null +++ b/tools/regz/src/Register.zig @@ -0,0 +1,4 @@ +name: []const u8, +description: ?[]const u8, +addr_offset: usize, +size: usize, diff --git a/tools/regz/src/atdf.zig b/tools/regz/src/atdf.zig new file mode 100644 index 0000000..3ea1e9a --- /dev/null +++ b/tools/regz/src/atdf.zig @@ -0,0 +1,92 @@ +const std = @import("std"); +const xml = @import("xml.zig"); +const Peripheral = @import("Peripheral.zig"); +const Register = @import("Register.zig"); +const Field = @import("Field.zig"); + +const ArenaAllocator = std.heap.ArenaAllocator; +const Allocator = std.mem.Allocator; + +pub fn parsePeripheral(arena: *ArenaAllocator, node: *xml.Node) !Peripheral { + const allocator = arena.allocator(); + return Peripheral{ + .name = try allocator.dupe(u8, xml.getAttribute(node, "name") orelse return error.NoName), + .version = if (xml.getAttribute(node, "version")) |version| + try allocator.dupe(u8, version) + else + null, + .description = if (xml.getAttribute(node, "caption")) |caption| + try allocator.dupe(u8, caption) + else + null, + // TODO: make sure this is always null + .base_addr = null, + }; +} + +pub fn parseRegister(arena: *ArenaAllocator, node: *xml.Node, regs_start_addr: usize, has_fields: bool) !Register { + const allocator = arena.allocator(); + return Register{ + .name = try allocator.dupe(u8, xml.getAttribute(node, "name") orelse return error.NoName), + .description = if (xml.getAttribute(node, "caption")) |caption| + try allocator.dupe(u8, caption) + else + null, + .addr_offset = if (xml.getAttribute(node, "offset")) |reg_offset_str| + regs_start_addr + try std.fmt.parseInt(usize, reg_offset_str, 0) + else + return error.NoAddrOffset, + .size = if (xml.getAttribute(node, "size")) |size_str| blk: { + const full_size = 8 * try std.fmt.parseInt(usize, size_str, 0); + if (!has_fields) { + const mask = try std.fmt.parseInt(u64, xml.getAttribute(node, "mask") orelse break :blk full_size, 0); + + // ensure it starts at bit 0 + if (mask & 0x1 == 0) + break :blk full_size; + + var cursor: u7 = 0; + while ((mask & (@as(u64, 1) << @intCast(u6, cursor))) != 0 and cursor < @bitSizeOf(u64)) : (cursor += 1) {} + + // we now have width, make sure that the mask is contiguous + const width = cursor; + while (cursor < @bitSizeOf(u64)) : (cursor += 1) { + if ((mask & (@as(u64, 1) << @intCast(u6, cursor))) != 0) { + break :blk full_size; + } + } else break :blk width; + } + + break :blk full_size; + } else return error.NoSize, // if this shows up then we need to determine the default size of a register + }; +} + +pub fn parseField(arena: *ArenaAllocator, node: *xml.Node) !Field { + const allocator = arena.allocator(); + const name = try allocator.dupe(u8, xml.getAttribute(node, "name") orelse return error.NoName); + const mask = try std.fmt.parseInt(u64, xml.getAttribute(node, "mask") orelse return error.NoMask, 0); + + var offset: u7 = 0; + while ((mask & (@as(u64, 1) << @intCast(u6, offset))) == 0 and offset < @bitSizeOf(usize)) : (offset += 1) {} + + var cursor: u7 = offset; + while ((mask & (@as(u64, 1) << @intCast(u6, cursor))) != 0 and cursor < @bitSizeOf(u64)) : (cursor += 1) {} + + const width = cursor - offset; + while (cursor < @bitSizeOf(u64)) : (cursor += 1) + if ((mask & (@as(u64, 1) << @intCast(u6, cursor))) != 0) { + std.log.warn("found mask with discontinuous bits: {s}, ignoring", .{name}); + return error.InvalidMask; + }; + + return Field{ + .name = name, + .description = if (xml.getAttribute(node, "caption")) |caption| + try allocator.dupe(u8, caption) + else + null, + .offset = offset, + .width = width, + }; +} diff --git a/tools/regz/src/main.zig b/tools/regz/src/main.zig index 0274fa5..c1094c7 100644 --- a/tools/regz/src/main.zig +++ b/tools/regz/src/main.zig @@ -14,13 +14,8 @@ const svd_schema = @embedFile("cmsis-svd.xsd"); const params = [_]clap.Param(clap.Help){ clap.parseParam("-h, --help Display this help and exit") catch unreachable, - clap.parseParam("-s, --schema Explicitly set schema type, one of: svd, atdf, json") catch unreachable, - clap.parseParam("...") catch unreachable, -}; - -const parsers = .{ - .TYPE = clap.parsers.string, - .POS = clap.parsers.string, + clap.parseParam("-s, --schema Explicitly set schema type, one of: svd, atdf, json") catch unreachable, + clap.parseParam("...") catch unreachable, }; pub fn main() !void { @@ -41,12 +36,17 @@ const Schema = enum { fn mainImpl() anyerror!void { defer xml.cleanupParser(); - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa = std.heap.GeneralPurposeAllocator(.{ + .stack_trace_frames = 20, + }){}; const allocator = gpa.allocator(); defer _ = gpa.deinit(); + var arena = ArenaAllocator.init(allocator); + defer arena.deinit(); + var diag = clap.Diagnostic{}; - var res = clap.parse(clap.Help, ¶ms, parsers, .{ .diagnostic = &diag }) catch |err| { + var res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ .diagnostic = &diag }) catch |err| { // Report useful error and exit diag.report(std.io.getStdErr().writer(), err) catch {}; return error.Explained; @@ -127,7 +127,7 @@ fn readFn(ctx: ?*anyopaque, buffer: ?[*]u8, len: c_int) callconv(.C) c_int { } else -1; } -fn parseXmlDatabase(allocator: std.mem.Allocator, doc: *xml.Doc, schema: Schema) !Database { +fn parseXmlDatabase(allocator: Allocator, doc: *xml.Doc, schema: Schema) !Database { return switch (schema) { .json => unreachable, .atdf => try Database.initFromAtdf(allocator, doc), diff --git a/tools/regz/src/svd.zig b/tools/regz/src/svd.zig index c9e6a21..b8a442c 100644 --- a/tools/regz/src/svd.zig +++ b/tools/regz/src/svd.zig @@ -1,27 +1,28 @@ const std = @import("std"); const xml = @import("xml.zig"); +const Peripheral = @import("Peripheral.zig"); +const Register = @import("Register.zig"); +const Field = @import("Field.zig"); const ArenaAllocator = std.heap.ArenaAllocator; const Allocator = std.mem.Allocator; -// TODO: normalize descriptions, watch out for explicit '\n's tho, we want to replicate those newlines in generated text - pub const Device = struct { - vendor: ?[]const u8, - vendor_id: ?[]const u8, - name: ?[]const u8, - series: ?[]const u8, - version: ?[]const u8, - description: ?[]const u8, - license_text: ?[]const u8, + vendor: ?[]const u8 = null, + vendor_id: ?[]const u8 = null, + name: ?[]const u8 = null, + series: ?[]const u8 = null, + version: ?[]const u8 = null, + description: ?[]const u8 = null, + license_text: ?[]const u8 = null, address_unit_bits: usize, width: usize, register_properties: struct { - size: ?usize, - access: ?Access, - protection: ?[]const u8, - reset_value: ?[]const u8, - reset_mask: ?[]const u8, + size: ?usize = null, + access: ?Access = null, + protection: ?[]const u8 = null, + reset_value: ?[]const u8 = null, + reset_mask: ?[]const u8 = null, }, pub fn parse(arena: *ArenaAllocator, nodes: *xml.Node) !Device { @@ -79,6 +80,9 @@ pub const CpuName = enum { cortex_a53, cortex_a57, cortex_a72, + + // avr + avr, other, // TODO: finish @@ -109,6 +113,8 @@ pub const CpuName = enum { CpuName.cortex_m4 else if (std.mem.eql(u8, "CM7", str)) CpuName.cortex_m7 + else if (std.mem.eql(u8, "AVR8", str)) + CpuName.avr else null; } @@ -186,25 +192,18 @@ pub const Access = enum { } }; -pub const Peripheral = struct { - name: []const u8, - version: ?[]const u8, - description: ?[]const u8, - base_addr: usize, - - pub fn parse(arena: *ArenaAllocator, nodes: *xml.Node) !Peripheral { - const allocator = arena.allocator(); - return Peripheral{ - .name = try allocator.dupe(u8, xml.findValueForKey(nodes, "name") orelse return error.NoName), - .version = if (xml.findValueForKey(nodes, "version")) |version| - try allocator.dupe(u8, version) - else - null, - .description = try xml.parseDescription(allocator, nodes, "description"), - .base_addr = (try xml.parseIntForKey(usize, arena.child_allocator, nodes, "baseAddress")) orelse return error.NoBaseAddr, // isDefault? - }; - } -}; +pub fn parsePeripheral(arena: *ArenaAllocator, nodes: *xml.Node) !Peripheral { + const allocator = arena.allocator(); + return Peripheral{ + .name = try allocator.dupe(u8, xml.findValueForKey(nodes, "name") orelse return error.NoName), + .version = if (xml.findValueForKey(nodes, "version")) |version| + try allocator.dupe(u8, version) + else + null, + .description = try xml.parseDescription(allocator, nodes, "description"), + .base_addr = (try xml.parseIntForKey(usize, arena.child_allocator, nodes, "baseAddress")) orelse return error.NoBaseAddr, // isDefault? + }; +} pub const Interrupt = struct { name: []const u8, @@ -234,22 +233,15 @@ pub const Interrupt = struct { } }; -pub const Register = struct { - name: []const u8, - description: ?[]const u8, - addr_offset: usize, - size: usize, - - pub fn parse(arena: *ArenaAllocator, nodes: *xml.Node, device_width: usize) !Register { - const allocator = arena.allocator(); - return Register{ - .name = try allocator.dupe(u8, xml.findValueForKey(nodes, "name") orelse return error.NoName), - .description = try xml.parseDescription(allocator, nodes, "description"), - .addr_offset = try std.fmt.parseInt(usize, xml.findValueForKey(nodes, "addressOffset") orelse return error.NoAddrOffset, 0), - .size = (try xml.parseIntForKey(usize, arena.child_allocator, nodes, "size")) orelse device_width, - }; - } -}; +pub fn parseRegister(arena: *ArenaAllocator, nodes: *xml.Node, device_width: usize) !Register { + const allocator = arena.allocator(); + return Register{ + .name = try allocator.dupe(u8, xml.findValueForKey(nodes, "name") orelse return error.NoName), + .description = try xml.parseDescription(allocator, nodes, "description"), + .addr_offset = try std.fmt.parseInt(usize, xml.findValueForKey(nodes, "addressOffset") orelse return error.NoAddrOffset, 0), + .size = (try xml.parseIntForKey(usize, arena.child_allocator, nodes, "size")) orelse device_width, + }; +} pub const Cluster = struct { name: []const u8, @@ -271,76 +263,62 @@ const BitRange = struct { width: u8, }; -pub const Field = struct { - name: []const u8, - description: ?[]const u8, - offset: u8, - width: u8, - - pub fn parse(arena: *ArenaAllocator, nodes: *xml.Node) !Field { - const allocator = arena.allocator(); - // TODO: - const bit_range = blk: { - const lsb_opt = xml.findValueForKey(nodes, "lsb"); - const msb_opt = xml.findValueForKey(nodes, "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; - - break :blk BitRange{ - .offset = lsb, - .width = msb - lsb + 1, - }; - } - - const bit_offset_opt = xml.findValueForKey(nodes, "bitOffset"); - const bit_width_opt = xml.findValueForKey(nodes, "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); - - break :blk BitRange{ - .offset = offset, - .width = width, - }; - } - - const bit_range_opt = xml.findValueForKey(nodes, "bitRange"); - if (bit_range_opt) |bit_range_str| { - var it = std.mem.tokenize(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; - - break :blk BitRange{ - .offset = lsb, - .width = msb - lsb + 1, - }; - } - - return error.InvalidRange; - }; - - return Field{ - .name = try allocator.dupe(u8, xml.findValueForKey(nodes, "name") orelse return error.NoName), - .offset = bit_range.offset, - .width = bit_range.width, - .description = try xml.parseDescription(allocator, nodes, "description"), - }; - } +pub fn parseField(arena: *ArenaAllocator, nodes: *xml.Node) !Field { + const allocator = arena.allocator(); + // TODO: + const bit_range = blk: { + const lsb_opt = xml.findValueForKey(nodes, "lsb"); + const msb_opt = xml.findValueForKey(nodes, "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; + + break :blk BitRange{ + .offset = lsb, + .width = msb - lsb + 1, + }; + } + + const bit_offset_opt = xml.findValueForKey(nodes, "bitOffset"); + const bit_width_opt = xml.findValueForKey(nodes, "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); + + break :blk BitRange{ + .offset = offset, + .width = width, + }; + } + + const bit_range_opt = xml.findValueForKey(nodes, "bitRange"); + if (bit_range_opt) |bit_range_str| { + var it = std.mem.tokenize(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; + + break :blk BitRange{ + .offset = lsb, + .width = msb - lsb + 1, + }; + } + + return error.InvalidRange; + }; - pub fn lessThan(_: void, lhs: Field, rhs: Field) bool { - return if (lhs.offset == rhs.offset) - lhs.width < rhs.width - else - lhs.offset < rhs.offset; - } -}; + return Field{ + .name = try allocator.dupe(u8, xml.findValueForKey(nodes, "name") orelse return error.NoName), + .offset = bit_range.offset, + .width = bit_range.width, + .description = try xml.parseDescription(allocator, nodes, "description"), + }; +} pub const EnumeratedValue = struct { name: []const u8,