You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1449 lines
46 KiB
Zig

//! Code generation and associated tests
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const Database = @import("Database.zig");
const EntityId = Database.EntityId;
const EntitySet = Database.EntitySet;
const arm = @import("arch/arm.zig");
const avr = @import("arch/avr.zig");
const log = std.log.scoped(.gen);
const EntityWithOffsetAndSize = struct {
id: EntityId,
offset: u64,
size: u64,
};
const EntityWithOffset = struct {
id: EntityId,
offset: u64,
fn less_than(_: void, lhs: EntityWithOffset, rhs: EntityWithOffset) bool {
return lhs.offset < rhs.offset;
}
};
pub fn to_zig(db: Database, out_writer: anytype) !void {
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
const writer = buffer.writer();
try writer.writeAll(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
);
try write_devices(db, writer);
try write_types(db, writer);
try writer.writeByte(0);
// format the generated code
var ast = try std.zig.Ast.parse(db.gpa, buffer.items[0 .. buffer.items.len - 1 :0], .zig);
defer ast.deinit(db.gpa);
// TODO: ast check?
const text = try ast.render(db.gpa);
defer db.gpa.free(text);
try out_writer.writeAll(text);
}
fn write_devices(db: Database, writer: anytype) !void {
if (db.instances.devices.count() == 0)
return;
try writer.writeAll(
\\
\\pub const devices = struct {
\\
);
// TODO: order devices alphabetically
for (db.instances.devices.keys()) |device_id| {
write_device(db, device_id, writer) catch |err| {
log.warn("failed to write device: {}", .{err});
};
}
try writer.writeAll("};\n");
}
pub fn write_comment(allocator: Allocator, comment: []const u8, writer: anytype) !void {
var tokenized = std.ArrayList(u8).init(allocator);
defer tokenized.deinit();
var first = true;
var tok_it = std.mem.tokenizeAny(u8, comment, "\n\r \t");
while (tok_it.next()) |token| {
if (!first)
first = false
else
try tokenized.writer().writeByte(' ');
try tokenized.writer().writeAll(token);
}
const unescaped = try std.mem.replaceOwned(u8, allocator, tokenized.items, "\\n", "\n");
defer allocator.free(unescaped);
var line_it = std.mem.tokenizeScalar(u8, unescaped, '\n');
while (line_it.next()) |line|
try writer.print("/// {s}\n", .{line});
}
fn write_string(str: []const u8, writer: anytype) !void {
if (std.mem.containsAtLeast(u8, str, 1, "\n")) {
try writer.writeByte('\n');
var line_it = std.mem.splitScalar(u8, str, '\n');
while (line_it.next()) |line|
try writer.print("\\\\{s}\n", .{line});
} else {
try writer.print("\"{s}\"", .{str});
}
}
fn write_device(db: Database, device_id: EntityId, out_writer: anytype) !void {
assert(db.entity_is("instance.device", device_id));
const name = db.attrs.name.get(device_id) orelse return error.MissingDeviceName;
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
const writer = buffer.writer();
// TODO: multiline?
if (db.attrs.description.get(device_id)) |description|
try write_comment(db.arena.allocator(), description, writer);
try writer.print(
\\pub const {} = struct {{
\\
, .{std.zig.fmtId(name)});
// TODO: alphabetic order
const properties = db.instances.devices.get(device_id).?.properties;
if (properties.count() > 0) {
try writer.writeAll("pub const properties = struct {\n");
var it = properties.iterator();
while (it.next()) |entry| {
try writer.print("pub const {} = ", .{
std.zig.fmtId(entry.key_ptr.*),
});
try write_string(entry.value_ptr.*, writer);
try writer.writeAll(";\n");
}
try writer.writeAll("};\n\n");
}
write_vector_table(db, device_id, writer) catch |err|
log.warn("failed to write vector table: {}", .{err});
if (db.children.peripherals.get(device_id)) |peripheral_set| {
var list = std.ArrayList(EntityWithOffset).init(db.gpa);
defer list.deinit();
for (peripheral_set.keys()) |peripheral_id| {
const offset = db.attrs.offset.get(peripheral_id) orelse return error.MissingPeripheralInstanceOffset;
try list.append(.{ .id = peripheral_id, .offset = offset });
}
std.sort.insertion(EntityWithOffset, list.items, {}, EntityWithOffset.less_than);
try writer.writeAll("pub const peripherals = struct {\n");
for (list.items) |periph|
write_peripheral_instance(db, periph.id, periph.offset, writer) catch |err| {
log.warn("failed to serialize peripheral instance: {}", .{err});
};
try writer.writeAll("};\n");
}
try writer.writeAll("};\n");
try out_writer.writeAll(buffer.items);
}
// generates a string for a type in the `types` namespace of the generated
// code. Since this is only used in code generation, just going to stuff it in
// the arena allocator
fn types_reference(db: Database, type_id: EntityId) ![]const u8 {
// TODO: assert type_id is a type
var full_name_components = std.ArrayList([]const u8).init(db.gpa);
defer full_name_components.deinit();
var id = type_id;
// hard limit for walking up the tree, if we hit it there's a bug
const count_max: u32 = 8;
var count: u32 = 0;
while (count < count_max) : (count += 1) {
if (db.attrs.name.get(id)) |next_name|
try full_name_components.insert(0, next_name)
else
return error.MissingTypeName;
if (db.attrs.parent.get(id)) |parent_id|
id = parent_id
else
break;
} else @panic("hit limit for reference length");
if (full_name_components.items.len == 0)
return error.CantReference;
var full_name = std.ArrayList(u8).init(db.arena.allocator());
const writer = full_name.writer();
try writer.writeAll("types");
// determine the namespace under 'types' the reference is under
const root_parent_entity_type = db.get_entity_type(id).?;
inline for (@typeInfo(Database.EntityType).Enum.fields) |field| {
if (root_parent_entity_type == @field(Database.EntityType, field.name)) {
try writer.print(".{s}s", .{field.name});
break;
}
}
for (full_name_components.items) |component|
try writer.print(".{}", .{
std.zig.fmtId(component),
});
return full_name.toOwnedSlice();
}
fn write_vector_table(
db: Database,
device_id: EntityId,
out_writer: anytype,
) !void {
assert(db.entity_is("instance.device", device_id));
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
const writer = buffer.writer();
const arch = db.instances.devices.get(device_id).?.arch;
if (arch.is_arm())
try arm.write_interrupt_vector(db, device_id, writer)
else if (arch.is_avr())
try avr.write_interrupt_vector(db, device_id, writer)
else if (arch == .unknown)
return
else
unreachable;
try out_writer.writeAll(buffer.items);
}
fn write_peripheral_instance(db: Database, instance_id: EntityId, offset: u64, out_writer: anytype) !void {
assert(db.entity_is("instance.peripheral", instance_id));
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
const writer = buffer.writer();
const name = db.attrs.name.get(instance_id) orelse return error.MissingPeripheralInstanceName;
const type_id = db.instances.peripherals.get(instance_id).?;
assert(db.attrs.name.contains(type_id));
const type_ref = try types_reference(db, type_id);
if (db.attrs.description.get(instance_id)) |description|
try write_comment(db.arena.allocator(), description, writer)
else if (db.attrs.description.get(type_id)) |description|
try write_comment(db.arena.allocator(), description, writer);
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 {}: *volatile {s}{s} = @ptrFromInt(0x{x});\n", .{
std.zig.fmtId(name),
array_prefix,
type_ref,
offset,
});
try out_writer.writeAll(buffer.items);
}
// Top level types are any types without a parent. In order for them to be
// rendered in the `types` namespace they need a name
fn has_top_level_named_types(db: Database) bool {
inline for (@typeInfo(@TypeOf(db.types)).Struct.fields) |field| {
for (@field(db.types, field.name).keys()) |id| {
if (!db.attrs.parent.contains(id) and
db.attrs.name.contains(id))
{
return true;
}
}
}
return false;
}
fn write_types(db: Database, writer: anytype) !void {
if (!has_top_level_named_types(db))
return;
try writer.writeAll(
\\
\\pub const types = struct {
\\
);
if (db.types.peripherals.count() > 0) {
try writer.writeAll("pub const peripherals = struct {\n");
for (db.types.peripherals.keys()) |peripheral_id| {
write_peripheral(db, peripheral_id, writer) catch |err| {
log.warn("failed to generate peripheral '{s}': {}", .{
db.attrs.name.get(peripheral_id) orelse "<unknown>",
err,
});
};
}
try writer.writeAll("};\n");
}
try writer.writeAll("};\n");
}
// a peripheral is zero sized if it doesn't have any registers, and if none of
// its register groups have an offset
fn is_peripheral_zero_sized(db: Database, peripheral_id: EntityId) bool {
if (db.children.registers.contains(peripheral_id)) {
return false;
} else {
log.debug("no registers found", .{});
}
return if (db.children.register_groups.get(peripheral_id)) |register_group_set| blk: {
for (register_group_set.keys()) |register_group_id| {
if (db.attrs.offset.contains(register_group_id))
break :blk false;
}
break :blk true;
} else true;
}
fn write_peripheral(
db: Database,
peripheral_id: EntityId,
out_writer: anytype,
) !void {
assert(db.entity_is("type.peripheral", peripheral_id) or
db.entity_is("type.register_group", peripheral_id));
// peripheral types should always have a name (responsibility of parsing to get this done)
const name = db.attrs.name.get(peripheral_id) orelse unreachable;
// for now only serialize flat peripherals with no register groups
// TODO: expand this
if (db.children.register_groups.get(peripheral_id)) |register_group_set| {
for (register_group_set.keys()) |register_group_id| {
if (db.attrs.offset.contains(register_group_id)) {
log.warn("TODO: implement register groups with offset in peripheral type ({s})", .{name});
return;
}
}
}
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
var registers = try get_ordered_register_list(db, peripheral_id);
defer registers.deinit();
const writer = buffer.writer();
try writer.writeByte('\n');
if (db.attrs.description.get(peripheral_id)) |description|
try write_comment(db.arena.allocator(), description, writer);
const zero_sized = is_peripheral_zero_sized(db, peripheral_id);
const has_modes = db.children.modes.contains(peripheral_id);
try writer.print(
\\pub const {} = {s} {s} {{
\\
, .{
std.zig.fmtId(name),
if (zero_sized) "" else "extern",
if (has_modes) "union" else "struct",
});
var written = false;
if (db.children.modes.get(peripheral_id)) |mode_set| {
try write_newline_if_written(writer, &written);
try write_mode_enum_and_fn(db, mode_set, writer);
}
if (db.children.enums.get(peripheral_id)) |enum_set|
try write_enums(db, &written, enum_set, writer);
// namespaced registers
if (db.children.register_groups.get(peripheral_id)) |register_group_set| {
for (register_group_set.keys()) |register_group_id| {
// a register group with an offset means that it has a location within the peripheral
if (db.attrs.offset.contains(register_group_id))
continue;
try write_newline_if_written(writer, &written);
try write_peripheral(db, register_group_id, writer);
}
}
try write_newline_if_written(writer, &written);
try write_registers(db, peripheral_id, writer);
try writer.writeAll("\n}");
try writer.writeAll(";\n");
try out_writer.writeAll(buffer.items);
}
fn write_newline_if_written(writer: anytype, written: *bool) !void {
if (written.*)
try writer.writeByte('\n')
else
written.* = true;
}
fn write_enums(db: Database, written: *bool, enum_set: EntitySet, writer: anytype) !void {
for (enum_set.keys()) |enum_id| {
try write_newline_if_written(writer, written);
try write_enum(db, enum_id, writer);
}
}
fn write_enum(db: Database, enum_id: EntityId, out_writer: anytype) !void {
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
const writer = buffer.writer();
const name = db.attrs.name.get(enum_id) orelse return;
const size = db.attrs.size.get(enum_id) orelse return error.MissingEnumSize;
// TODO: handle this instead of assert
// assert(std.math.ceilPowerOfTwo(field_set.count()) <= size);
if (db.attrs.description.get(enum_id)) |description|
try write_comment(db.arena.allocator(), description, writer);
try writer.print("pub const {} = enum(u{}) {{\n", .{
std.zig.fmtId(name),
size,
});
try write_enum_fields(db, enum_id, writer);
try writer.writeAll("};\n");
try out_writer.writeAll(buffer.items);
}
fn write_enum_fields(db: Database, enum_id: u32, out_writer: anytype) !void {
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
const writer = buffer.writer();
const size = db.attrs.size.get(enum_id) orelse return error.MissingEnumSize;
const field_set = db.children.enum_fields.get(enum_id) orelse return error.MissingEnumFields;
for (field_set.keys()) |enum_field_id|
try write_enum_field(db, enum_field_id, size, writer);
// if the enum doesn't completely fill the integer then make it a non-exhaustive enum
if (field_set.count() < std.math.pow(u64, 2, size))
try writer.writeAll("_,\n");
try out_writer.writeAll(buffer.items);
}
fn write_enum_field(
db: Database,
enum_field_id: EntityId,
size: u64,
writer: anytype,
) !void {
const name = db.attrs.name.get(enum_field_id) orelse return error.MissingEnumFieldName;
const value = db.types.enum_fields.get(enum_field_id) orelse return error.MissingEnumFieldValue;
// TODO: use size to print the hex value (pad with zeroes accordingly)
_ = size;
if (db.attrs.description.get(enum_field_id)) |description|
try write_comment(db.arena.allocator(), description, writer);
try writer.print("{} = 0x{x},\n", .{ std.zig.fmtId(name), value });
}
fn write_mode_enum_and_fn(
db: Database,
mode_set: EntitySet,
out_writer: anytype,
) !void {
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
const writer = buffer.writer();
try writer.writeAll("pub const Mode = enum {\n");
for (mode_set.keys()) |mode_id| {
const mode_name = db.attrs.name.get(mode_id) orelse unreachable;
try writer.print("{},\n", .{std.zig.fmtId(mode_name)});
}
try writer.writeAll(
\\};
\\
\\pub fn get_mode(self: *volatile @This()) Mode {
\\
);
for (mode_set.keys()) |mode_id| {
const mode_name = db.attrs.name.get(mode_id) orelse unreachable;
var components = std.ArrayList([]const u8).init(db.gpa);
defer components.deinit();
const mode = db.types.modes.get(mode_id).?;
var tok_it = std.mem.tokenizeScalar(u8, mode.qualifier, '.');
while (tok_it.next()) |token|
try components.append(token);
const field_name = components.items[components.items.len - 1];
_ = try db.get_entity_id_by_name("type.field", field_name);
const access_path = try std.mem.join(db.arena.allocator(), ".", components.items[1 .. components.items.len - 1]);
try writer.writeAll("{\n");
try writer.print("const value = self.{s}.read().{s};\n", .{
access_path,
field_name,
});
try writer.writeAll("switch (value) {\n");
tok_it = std.mem.tokenizeScalar(u8, mode.value, ' ');
while (tok_it.next()) |token| {
const value = try std.fmt.parseInt(u64, token, 0);
try writer.print("{},\n", .{value});
}
try writer.print("=> return .{},\n", .{std.zig.fmtId(mode_name)});
try writer.writeAll("else => {},\n");
try writer.writeAll("}\n");
try writer.writeAll("}\n");
}
try writer.writeAll("\nunreachable;\n");
try writer.writeAll("}\n");
try out_writer.writeAll(buffer.items);
}
fn write_registers(db: Database, parent_id: EntityId, out_writer: anytype) !void {
var registers = try get_ordered_register_list(db, parent_id);
defer registers.deinit();
if (db.children.modes.get(parent_id)) |modes|
try write_registers_with_modes(db, parent_id, modes, registers, out_writer)
else
try write_registers_base(db, parent_id, registers.items, out_writer);
}
fn write_registers_with_modes(
db: Database,
parent_id: EntityId,
mode_set: EntitySet,
registers: std.ArrayList(EntityWithOffset),
out_writer: anytype,
) !void {
const allocator = db.arena.allocator();
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
const writer = buffer.writer();
for (mode_set.keys()) |mode_id| {
const mode_name = db.attrs.name.get(mode_id) orelse unreachable;
// filter registers for this mode
var moded_registers = std.ArrayList(EntityWithOffset).init(allocator);
for (registers.items) |register| {
if (db.attrs.modes.get(register.id)) |reg_mode_set| {
for (reg_mode_set.keys()) |reg_mode_id| {
if (reg_mode_id == mode_id)
try moded_registers.append(register);
}
// if no mode is specified, then it should always be present
} else try moded_registers.append(register);
}
try writer.print("{}: extern struct {{\n", .{
std.zig.fmtId(mode_name),
});
try write_registers_base(db, parent_id, moded_registers.items, writer);
try writer.writeAll("},\n");
}
try out_writer.writeAll(buffer.items);
}
fn write_registers_base(
db: Database,
parent_id: EntityId,
registers: []const EntityWithOffset,
out_writer: anytype,
) !void {
// registers _should_ be sorted when then make their way here
assert(std.sort.isSorted(EntityWithOffset, registers, {}, EntityWithOffset.less_than));
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
const writer = buffer.writer();
// 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 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,
};
}
break :blk ret orelse unreachable;
};
try write_register(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);
}
fn write_register(
db: Database,
register_id: EntityId,
out_writer: anytype,
) !void {
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
const name = db.attrs.name.get(register_id) orelse unreachable;
const size = db.attrs.size.get(register_id) orelse unreachable;
const writer = buffer.writer();
if (db.attrs.description.get(register_id)) |description|
try write_comment(db.arena.allocator(), description, writer);
var array_prefix_buf: [80]u8 = undefined;
const array_prefix: []const u8 = if (db.attrs.count.get(register_id)) |count|
try std.fmt.bufPrint(&array_prefix_buf, "[{}]", .{count})
else
"";
if (db.attrs.group.contains(register_id)) {
try writer.print("{}: {s}{},\n", .{
std.zig.fmtId(name),
array_prefix,
std.zig.fmtId(name),
});
} else if (db.children.fields.get(register_id)) |field_set| {
var fields = std.ArrayList(EntityWithOffset).init(db.gpa);
defer fields.deinit();
for (field_set.keys()) |field_id|
try fields.append(.{
.id = field_id,
.offset = db.attrs.offset.get(field_id) orelse continue,
});
std.sort.insertion(EntityWithOffset, fields.items, {}, EntityWithOffset.less_than);
try writer.print("{}: {s}mmio.Mmio(packed struct(u{}) {{\n", .{
std.zig.fmtId(name),
array_prefix,
size,
});
try write_fields(db, fields.items, size, writer);
try writer.writeAll("}),\n");
} else try writer.print("{}: {s}u{},\n", .{
std.zig.fmtId(name),
array_prefix,
size,
});
try out_writer.writeAll(buffer.items);
}
fn write_fields(
db: Database,
fields: []const EntityWithOffset,
register_size: u64,
out_writer: anytype,
) !void {
assert(std.sort.isSorted(EntityWithOffset, fields, {}, EntityWithOffset.less_than));
var buffer = std.ArrayList(u8).init(db.arena.allocator());
defer buffer.deinit();
// don't have to care about modes
// prioritize smaller fields that come earlier
const writer = buffer.writer();
var offset: u64 = 0;
var i: u32 = 0;
while (i < fields.len and offset < register_size) {
if (offset < fields[i].offset) {
try writer.print("reserved{}: u{},\n", .{ fields[i].offset, fields[i].offset - offset });
offset = fields[i].offset;
} else if (offset > fields[i].offset) {
if (db.attrs.name.get(fields[i].id)) |name|
log.warn("skipping field: {s}, offset={}, field_offset={}", .{
name,
offset,
fields[i].offset,
});
i += 1;
continue;
}
var end = i;
while (end < fields.len and fields[end].offset == offset) : (end += 1) {}
const next = blk: {
var ret: ?EntityWithOffsetAndSize = null;
for (fields[i..end]) |field| {
const size = if (db.attrs.size.get(field.id)) |size|
if (db.attrs.count.get(field.id)) |count|
size * count
else
size
else
unreachable;
if (ret == null or (size < ret.?.size))
ret = .{
.id = field.id,
.offset = field.offset,
.size = size,
};
}
break :blk ret orelse unreachable;
};
const name = db.attrs.name.get(next.id) orelse unreachable;
if (offset + next.size > register_size) {
log.warn("register '{s}' is outside register boundaries: offset={}, size={}, register_size={}", .{
name,
next.offset,
next.size,
register_size,
});
break;
}
if (db.attrs.description.get(next.id)) |description|
try write_comment(db.arena.allocator(), description, writer);
if (db.attrs.count.get(fields[i].id)) |count| {
if (db.attrs.@"enum".contains(fields[i].id))
log.warn("TODO: field array with enums", .{});
try writer.print("{}: packed struct(u{}) {{ ", .{
std.zig.fmtId(name),
next.size,
});
var j: u32 = 0;
while (j < count) : (j += 1) {
if (j > 0)
try writer.writeAll(", ");
try writer.print("u{}", .{next.size / count});
}
try writer.writeAll(" },\n");
} else if (db.attrs.@"enum".get(fields[i].id)) |enum_id| {
if (db.attrs.name.get(enum_id)) |enum_name| {
try writer.print(
\\{}: packed union {{
\\ raw: u{},
\\ value: {},
\\}},
\\
, .{
std.zig.fmtId(name),
next.size,
std.zig.fmtId(enum_name),
});
} else {
try writer.print(
\\{}: packed union {{
\\ raw: u{},
\\ value: enum(u{}) {{
\\
, .{
std.zig.fmtId(name),
next.size,
next.size,
});
try write_enum_fields(db, enum_id, writer);
try writer.writeAll("},\n},\n");
}
} else {
try writer.print("{s}: u{},\n", .{ name, next.size });
}
offset += next.size;
i = end;
}
assert(offset <= register_size);
if (offset < register_size)
try writer.print("padding: u{},\n", .{register_size - offset});
try out_writer.writeAll(buffer.items);
}
fn get_ordered_register_list(
db: Database,
parent_id: EntityId,
) !std.ArrayList(EntityWithOffset) {
var registers = std.ArrayList(EntityWithOffset).init(db.gpa);
errdefer registers.deinit();
// get list of registers
if (db.children.registers.get(parent_id)) |register_set| {
for (register_set.keys()) |register_id| {
const offset = db.attrs.offset.get(register_id) orelse continue;
try registers.append(.{ .id = register_id, .offset = offset });
}
}
std.sort.insertion(EntityWithOffset, registers.items, {}, EntityWithOffset.less_than);
return registers;
}
const tests = @import("output_tests.zig");
test "gen.peripheral type with register and field" {
var db = try tests.peripheral_type_with_register_and_field(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const TEST_PERIPHERAL = extern struct {
\\ TEST_REGISTER: mmio.Mmio(packed struct(u32) {
\\ TEST_FIELD: u1,
\\ padding: u31,
\\ }),
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.peripheral instantiation" {
var db = try tests.peripheral_instantiation(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const devices = struct {
\\ pub const TEST_DEVICE = struct {
\\ pub const peripherals = struct {
\\ pub const TEST0: *volatile types.peripherals.TEST_PERIPHERAL = @ptrFromInt(0x1000);
\\ };
\\ };
\\};
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const TEST_PERIPHERAL = extern struct {
\\ TEST_REGISTER: mmio.Mmio(packed struct(u32) {
\\ TEST_FIELD: u1,
\\ padding: u31,
\\ }),
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.peripherals with a shared type" {
var db = try tests.peripherals_with_shared_type(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const devices = struct {
\\ pub const TEST_DEVICE = struct {
\\ pub const peripherals = struct {
\\ pub const TEST0: *volatile types.peripherals.TEST_PERIPHERAL = @ptrFromInt(0x1000);
\\ pub const TEST1: *volatile types.peripherals.TEST_PERIPHERAL = @ptrFromInt(0x2000);
\\ };
\\ };
\\};
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const TEST_PERIPHERAL = extern struct {
\\ TEST_REGISTER: mmio.Mmio(packed struct(u32) {
\\ TEST_FIELD: u1,
\\ padding: u31,
\\ }),
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.peripheral with modes" {
var db = try tests.peripheral_with_modes(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const TEST_PERIPHERAL = extern union {
\\ pub const Mode = enum {
\\ TEST_MODE1,
\\ TEST_MODE2,
\\ };
\\
\\ pub fn get_mode(self: *volatile @This()) Mode {
\\ {
\\ const value = self.TEST_MODE1.COMMON_REGISTER.read().TEST_FIELD;
\\ switch (value) {
\\ 0,
\\ => return .TEST_MODE1,
\\ else => {},
\\ }
\\ }
\\ {
\\ const value = self.TEST_MODE2.COMMON_REGISTER.read().TEST_FIELD;
\\ switch (value) {
\\ 1,
\\ => return .TEST_MODE2,
\\ else => {},
\\ }
\\ }
\\
\\ unreachable;
\\ }
\\
\\ TEST_MODE1: extern struct {
\\ TEST_REGISTER1: u32,
\\ COMMON_REGISTER: mmio.Mmio(packed struct(u32) {
\\ TEST_FIELD: u1,
\\ padding: u31,
\\ }),
\\ },
\\ TEST_MODE2: extern struct {
\\ TEST_REGISTER2: u32,
\\ COMMON_REGISTER: mmio.Mmio(packed struct(u32) {
\\ TEST_FIELD: u1,
\\ padding: u31,
\\ }),
\\ },
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.peripheral with enum" {
var db = try tests.peripheral_with_enum(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const TEST_PERIPHERAL = extern struct {
\\ pub const TEST_ENUM = enum(u4) {
\\ TEST_ENUM_FIELD1 = 0x0,
\\ TEST_ENUM_FIELD2 = 0x1,
\\ _,
\\ };
\\
\\ TEST_REGISTER: u8,
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.peripheral with enum, enum is exhausted of values" {
var db = try tests.peripheral_with_enum_and_its_exhausted_of_values(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const TEST_PERIPHERAL = extern struct {
\\ pub const TEST_ENUM = enum(u1) {
\\ TEST_ENUM_FIELD1 = 0x0,
\\ TEST_ENUM_FIELD2 = 0x1,
\\ };
\\
\\ TEST_REGISTER: u8,
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.field with named enum" {
var db = try tests.field_with_named_enum(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const TEST_PERIPHERAL = extern struct {
\\ pub const TEST_ENUM = enum(u4) {
\\ TEST_ENUM_FIELD1 = 0x0,
\\ TEST_ENUM_FIELD2 = 0x1,
\\ _,
\\ };
\\
\\ TEST_REGISTER: mmio.Mmio(packed struct(u8) {
\\ TEST_FIELD: packed union {
\\ raw: u4,
\\ value: TEST_ENUM,
\\ },
\\ padding: u4,
\\ }),
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.field with anonymous enum" {
var db = try tests.field_with_anonymous_enum(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const TEST_PERIPHERAL = extern struct {
\\ TEST_REGISTER: mmio.Mmio(packed struct(u8) {
\\ TEST_FIELD: packed union {
\\ raw: u4,
\\ value: enum(u4) {
\\ TEST_ENUM_FIELD1 = 0x0,
\\ TEST_ENUM_FIELD2 = 0x1,
\\ _,
\\ },
\\ },
\\ padding: u4,
\\ }),
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.namespaced register groups" {
var db = try tests.namespaced_register_groups(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const devices = struct {
\\ pub const ATmega328P = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB: *volatile types.peripherals.PORT.PORTB = @ptrFromInt(0x23);
\\ pub const PORTC: *volatile types.peripherals.PORT.PORTC = @ptrFromInt(0x26);
\\ };
\\ };
\\};
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const PORT = struct {
\\ pub const PORTB = extern struct {
\\ PORTB: u8,
\\ DDRB: u8,
\\ PINB: u8,
\\ };
\\
\\ pub const PORTC = extern struct {
\\ PORTC: u8,
\\ DDRC: u8,
\\ PINC: u8,
\\ };
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.peripheral with reserved register" {
var db = try tests.peripheral_with_reserved_register(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const devices = struct {
\\ pub const ATmega328P = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB: *volatile types.peripherals.PORTB = @ptrFromInt(0x23);
\\ };
\\ };
\\};
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB = extern struct {
\\ PORTB: u32,
\\ reserved8: [4]u8,
\\ PINB: u32,
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.peripheral with count" {
var db = try tests.peripheral_with_count(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const devices = struct {
\\ pub const ATmega328P = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB: *volatile [4]types.peripherals.PORTB = @ptrFromInt(0x23);
\\ };
\\ };
\\};
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB = extern struct {
\\ PORTB: u8,
\\ DDRB: u8,
\\ PINB: u8,
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.peripheral with count, padding required" {
var db = try tests.peripheral_with_count_padding_required(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const devices = struct {
\\ pub const ATmega328P = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB: *volatile [4]types.peripherals.PORTB = @ptrFromInt(0x23);
\\ };
\\ };
\\};
\\
\\pub const types = struct {
\\ pub const peripherals = 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 tests.register_with_count(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const devices = struct {
\\ pub const ATmega328P = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB: *volatile types.peripherals.PORTB = @ptrFromInt(0x23);
\\ };
\\ };
\\};
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB = extern struct {
\\ PORTB: [4]u8,
\\ DDRB: u8,
\\ PINB: u8,
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.register with count and fields" {
var db = try tests.register_with_count_and_fields(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const devices = struct {
\\ pub const ATmega328P = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB: *volatile types.peripherals.PORTB = @ptrFromInt(0x23);
\\ };
\\ };
\\};
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB = extern struct {
\\ PORTB: [4]mmio.Mmio(packed struct(u8) {
\\ TEST_FIELD: u4,
\\ padding: u4,
\\ }),
\\ DDRB: u8,
\\ PINB: u8,
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.field with count, width of one, offset, and padding" {
var db = try tests.field_with_count_width_of_one_offset_and_padding(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB = extern struct {
\\ PORTB: mmio.Mmio(packed struct(u8) {
\\ reserved2: u2,
\\ TEST_FIELD: packed struct(u5) { u1, u1, u1, u1, u1 },
\\ padding: u1,
\\ }),
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.field with count, multi-bit width, offset, and padding" {
var db = try tests.field_with_count_multi_bit_width_offset_and_padding(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const types = struct {
\\ pub const peripherals = struct {
\\ pub const PORTB = extern struct {
\\ PORTB: mmio.Mmio(packed struct(u8) {
\\ reserved2: u2,
\\ TEST_FIELD: packed struct(u4) { u2, u2 },
\\ padding: u2,
\\ }),
\\ };
\\ };
\\};
\\
, buffer.items);
}
test "gen.interrupts.avr" {
var db = try tests.interrupts_avr(std.testing.allocator);
defer db.deinit();
var buffer = std.ArrayList(u8).init(std.testing.allocator);
defer buffer.deinit();
try db.to_zig(buffer.writer());
try std.testing.expectEqualStrings(
\\const micro = @import("microzig");
\\const mmio = micro.mmio;
\\
\\pub const devices = struct {
\\ pub const ATmega328P = struct {
\\ pub const VectorTable = extern struct {
\\ const Handler = micro.interrupt.Handler;
\\ const unhandled = micro.interrupt.unhandled;
\\
\\ RESET: Handler,
\\ TEST_VECTOR1: Handler = unhandled,
\\ reserved2: [1]u16 = undefined,
\\ TEST_VECTOR2: Handler = unhandled,
\\ };
\\ };
\\};
\\
, buffer.items);
}