├── .gitignore ├── LICENSE ├── README.md └── src ├── main.zig └── svd.zig /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | zig-out/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svd4zig 2 | 3 | Generate [Zig](https://ziglang.org/) header files from 4 | [CMSIS-SVD](http://www.keil.com/pack/doc/CMSIS/SVD/html/index.html) files for accessing MMIO 5 | registers. 6 | 7 | ## Features 8 | 9 | This is a fork of [this `svd2zig`](https://github.com/justinbalexander/svd2zig) that uses the output 10 | format based of [this other `svd2zig`](https://github.com/lynaghk/svd2zig). 11 | 12 | It's named `svd4zig` since it's `svd2zig * 2`. 13 | 14 | Features taken from justinbalexander's `svd2zig`: 15 | - This was the one used as a starting point 16 | - 100% in Zig 17 | - Naming conventions are taken from the datasheet (i.e. all caps), so it's easy to follow along 18 | - Strong Assumptions™ in the svd are targeted towards STM32 devices (the original used a 19 | STM32F767ZG, this fork was developed with an STM32F407) 20 | - The tool doesn't just output registers but also other information about the device (e.g. 21 | interrupts) 22 | 23 | Features taken from lynaghk's `svd2zig`: 24 | - Registers are modeled with packed structs (see [this 25 | post](https://scattered-thoughts.net/writing/mmio-in-zig) from the original authors) 26 | 27 | New features: 28 | - Unused bits are manually aligned to 8 bit boundaries to avoid incurring in [this 29 | bug](https://github.com/ziglang/zig/issues/2627) 30 | 31 | The entire specification is not completely supported yet, feel free to send pull requests to flesh 32 | out the parts of the specification that are missing for your project. 33 | 34 | ## Build: 35 | 36 | ``` 37 | zig build -Drelease-safe 38 | ``` 39 | 40 | ## Usage: 41 | 42 | ``` 43 | ./zig-cache/bin/svd4zig path/to/svd/file > path/to/output.zig 44 | zig fmt path/to/output.zig 45 | ``` 46 | 47 | ## Suggested location to find SVD file: 48 | 49 | https://github.com/posborne/cmsis-svd 50 | 51 | ## How to use the generated code: 52 | 53 | Have a look at [this blogpost](https://scattered-thoughts.net/writing/mmio-in-zig) for all the 54 | details, a short example to set and read some registers: 55 | 56 | ```zig 57 | // registers.zig is the generated file 58 | const regs = @import("registers.zig"); 59 | 60 | // Enable HSI 61 | regs.RCC.CR.modify(.{ .HSION = 1 }); 62 | 63 | // Wait for HSI ready 64 | while (regs.RCC.CR.read().HSIRDY != 1) {} 65 | 66 | // Select HSI as clock source 67 | regs.RCC.CFGR.modify(.{ .SW0 = 0, .SW1 = 0 }); 68 | 69 | // Enable external high-speed oscillator (HSE) 70 | regs.RCC.CR.modify(.{ .HSEON = 1 }); 71 | 72 | // Wait for HSE ready 73 | while (regs.RCC.CR.read().HSERDY != 1) {} 74 | ``` 75 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const ascii = std.ascii; 4 | const fmt = std.fmt; 5 | const warn = std.log.warn; 6 | 7 | const svd = @import("svd.zig"); 8 | 9 | var line_buffer: [1024 * 1024]u8 = undefined; 10 | 11 | const register_def = 12 | \\pub fn Register(comptime R: type) type { 13 | \\ return RegisterRW(R, R); 14 | \\} 15 | \\ 16 | \\pub fn RegisterRW(comptime Read: type, comptime Write: type) type { 17 | \\ return struct { 18 | \\ raw_ptr: *volatile u32, 19 | \\ 20 | \\ const Self = @This(); 21 | \\ 22 | \\ pub fn init(address: usize) Self { 23 | \\ return Self{ .raw_ptr = @intToPtr(*volatile u32, address) }; 24 | \\ } 25 | \\ 26 | \\ pub fn initRange(address: usize, comptime dim_increment: usize, comptime num_registers: usize) [num_registers]Self { 27 | \\ var registers: [num_registers]Self = undefined; 28 | \\ var i: usize = 0; 29 | \\ while (i < num_registers) : (i += 1) { 30 | \\ registers[i] = Self.init(address + (i * dim_increment)); 31 | \\ } 32 | \\ return registers; 33 | \\ } 34 | \\ 35 | \\ pub fn read(self: Self) Read { 36 | \\ return @bitCast(Read, self.raw_ptr.*); 37 | \\ } 38 | \\ 39 | \\ pub fn write(self: Self, value: Write) void { 40 | \\ // Forcing the alignment is a workaround for stores through 41 | \\ // volatile pointers generating multiple loads and stores. 42 | \\ // This is necessary for LLVM to generate code that can successfully 43 | \\ // modify MMIO registers that only allow word-sized stores. 44 | \\ // https://github.com/ziglang/zig/issues/8981#issuecomment-854911077 45 | \\ const aligned: Write align(4) = value; 46 | \\ self.raw_ptr.* = @ptrCast(*const u32, &aligned).*; 47 | \\ } 48 | \\ 49 | \\ pub fn modify(self: Self, new_value: anytype) void { 50 | \\ if (Read != Write) { 51 | \\ @compileError("Can't modify because read and write types for this register aren't the same."); 52 | \\ } 53 | \\ var old_value = self.read(); 54 | \\ const info = @typeInfo(@TypeOf(new_value)); 55 | \\ inline for (info.Struct.fields) |field| { 56 | \\ @field(old_value, field.name) = @field(new_value, field.name); 57 | \\ } 58 | \\ self.write(old_value); 59 | \\ } 60 | \\ 61 | \\ pub fn read_raw(self: Self) u32 { 62 | \\ return self.raw_ptr.*; 63 | \\ } 64 | \\ 65 | \\ pub fn write_raw(self: Self, value: u32) void { 66 | \\ self.raw_ptr.* = value; 67 | \\ } 68 | \\ 69 | \\ pub fn default_read_value(_: Self) Read { 70 | \\ return Read{}; 71 | \\ } 72 | \\ 73 | \\ pub fn default_write_value(_: Self) Write { 74 | \\ return Write{}; 75 | \\ } 76 | \\ }; 77 | \\} 78 | \\ 79 | ; 80 | 81 | pub fn main() anyerror!void { 82 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 83 | defer arena.deinit(); 84 | 85 | const allocator = arena.allocator(); 86 | 87 | var args = std.process.args(); 88 | 89 | _ = args.next(allocator); // skip application name 90 | // Note memory will be freed on exit since using arena 91 | 92 | const file_name = try args.next(allocator) orelse return error.MandatoryFilenameArgumentNotGiven; 93 | const file = try std.fs.cwd().openFile(file_name, .{ .read = true, .write = false }); 94 | 95 | const stream = &file.reader(); 96 | 97 | var state = SvdParseState.Device; 98 | var dev = try svd.Device.init(allocator); 99 | var cur_interrupt: svd.Interrupt = undefined; 100 | while (try stream.readUntilDelimiterOrEof(&line_buffer, '\n')) |line| { 101 | if (line.len == 0) { 102 | break; 103 | } 104 | var chunk = getChunk(line) orelse continue; 105 | switch (state) { 106 | .Device => { 107 | if (ascii.eqlIgnoreCase(chunk.tag, "/device")) { 108 | state = .Finished; 109 | } else if (ascii.eqlIgnoreCase(chunk.tag, "name")) { 110 | if (chunk.data) |data| { 111 | try dev.name.insertSlice(0, data); 112 | } 113 | } else if (ascii.eqlIgnoreCase(chunk.tag, "version")) { 114 | if (chunk.data) |data| { 115 | try dev.version.insertSlice(0, data); 116 | } 117 | } else if (ascii.eqlIgnoreCase(chunk.tag, "description")) { 118 | if (chunk.data) |data| { 119 | try dev.description.insertSlice(0, data); 120 | } 121 | } else if (ascii.eqlIgnoreCase(chunk.tag, "cpu")) { 122 | var cpu = try svd.Cpu.init(allocator); 123 | dev.cpu = cpu; 124 | state = .Cpu; 125 | } else if (ascii.eqlIgnoreCase(chunk.tag, "addressUnitBits")) { 126 | if (chunk.data) |data| { 127 | dev.address_unit_bits = fmt.parseInt(u32, data, 10) catch null; 128 | } 129 | } else if (ascii.eqlIgnoreCase(chunk.tag, "width")) { 130 | if (chunk.data) |data| { 131 | dev.max_bit_width = fmt.parseInt(u32, data, 10) catch null; 132 | } 133 | } else if (ascii.eqlIgnoreCase(chunk.tag, "size")) { 134 | if (chunk.data) |data| { 135 | dev.reg_default_size = fmt.parseInt(u32, data, 10) catch null; 136 | } 137 | } else if (ascii.eqlIgnoreCase(chunk.tag, "resetValue")) { 138 | if (chunk.data) |data| { 139 | dev.reg_default_reset_value = fmt.parseInt(u32, data, 10) catch null; 140 | } 141 | } else if (ascii.eqlIgnoreCase(chunk.tag, "resetMask")) { 142 | if (chunk.data) |data| { 143 | dev.reg_default_reset_mask = fmt.parseInt(u32, data, 10) catch null; 144 | } 145 | } else if (ascii.eqlIgnoreCase(chunk.tag, "peripherals")) { 146 | state = .Peripherals; 147 | } 148 | }, 149 | .Cpu => { 150 | if (ascii.eqlIgnoreCase(chunk.tag, "/cpu")) { 151 | state = .Device; 152 | } else if (ascii.eqlIgnoreCase(chunk.tag, "name")) { 153 | if (chunk.data) |data| { 154 | try dev.cpu.?.name.insertSlice(0, data); 155 | } 156 | } else if (ascii.eqlIgnoreCase(chunk.tag, "revision")) { 157 | if (chunk.data) |data| { 158 | try dev.cpu.?.revision.insertSlice(0, data); 159 | } 160 | } else if (ascii.eqlIgnoreCase(chunk.tag, "endian")) { 161 | if (chunk.data) |data| { 162 | try dev.cpu.?.endian.insertSlice(0, data); 163 | } 164 | } else if (ascii.eqlIgnoreCase(chunk.tag, "mpuPresent")) { 165 | if (chunk.data) |data| { 166 | dev.cpu.?.mpu_present = textToBool(data); 167 | } 168 | } else if (ascii.eqlIgnoreCase(chunk.tag, "fpuPresent")) { 169 | if (chunk.data) |data| { 170 | dev.cpu.?.fpu_present = textToBool(data); 171 | } 172 | } else if (ascii.eqlIgnoreCase(chunk.tag, "nvicPrioBits")) { 173 | if (chunk.data) |data| { 174 | dev.cpu.?.nvic_prio_bits = fmt.parseInt(u32, data, 10) catch null; 175 | } 176 | } else if (ascii.eqlIgnoreCase(chunk.tag, "vendorSystickConfig")) { 177 | if (chunk.data) |data| { 178 | dev.cpu.?.vendor_systick_config = textToBool(data); 179 | } 180 | } 181 | }, 182 | .Peripherals => { 183 | if (ascii.eqlIgnoreCase(chunk.tag, "/peripherals")) { 184 | state = .Device; 185 | } else if (ascii.eqlIgnoreCase(chunk.tag, "peripheral")) { 186 | if (chunk.derivedFrom) |derivedFrom| { 187 | for (dev.peripherals.items) |periph_being_checked| { 188 | if (mem.eql(u8, periph_being_checked.name.items, derivedFrom)) { 189 | try dev.peripherals.append(try periph_being_checked.copy(allocator)); 190 | state = .Peripheral; 191 | break; 192 | } 193 | } 194 | } else { 195 | var periph = try svd.Peripheral.init(allocator); 196 | try dev.peripherals.append(periph); 197 | state = .Peripheral; 198 | } 199 | } 200 | }, 201 | .Peripheral => { 202 | var cur_periph = &dev.peripherals.items[dev.peripherals.items.len - 1]; 203 | if (ascii.eqlIgnoreCase(chunk.tag, "/peripheral")) { 204 | state = .Peripherals; 205 | } else if (ascii.eqlIgnoreCase(chunk.tag, "name")) { 206 | if (chunk.data) |data| { 207 | // periph could be copy, must update periph name in sub-fields 208 | try cur_periph.name.replaceRange(0, cur_periph.name.items.len, data); 209 | for (cur_periph.registers.items) |*reg| { 210 | try reg.periph_containing.replaceRange(0, reg.periph_containing.items.len, data); 211 | for (reg.fields.items) |*field| { 212 | try field.periph.replaceRange(0, field.periph.items.len, data); 213 | } 214 | } 215 | } 216 | } else if (ascii.eqlIgnoreCase(chunk.tag, "description")) { 217 | if (chunk.data) |data| { 218 | try cur_periph.description.insertSlice(0, data); 219 | } 220 | } else if (ascii.eqlIgnoreCase(chunk.tag, "groupName")) { 221 | if (chunk.data) |data| { 222 | try cur_periph.group_name.insertSlice(0, data); 223 | } 224 | } else if (ascii.eqlIgnoreCase(chunk.tag, "baseAddress")) { 225 | if (chunk.data) |data| { 226 | cur_periph.base_address = parseHexLiteral(data); 227 | } 228 | } else if (ascii.eqlIgnoreCase(chunk.tag, "addressBlock")) { 229 | if (cur_periph.address_block) |_| { 230 | // do nothing 231 | } else { 232 | var block = try svd.AddressBlock.init(allocator); 233 | cur_periph.address_block = block; 234 | } 235 | state = .AddressBlock; 236 | } else if (ascii.eqlIgnoreCase(chunk.tag, "interrupt")) { 237 | cur_interrupt = try svd.Interrupt.init(allocator); 238 | state = .Interrupt; 239 | } else if (ascii.eqlIgnoreCase(chunk.tag, "registers")) { 240 | state = .Registers; 241 | } 242 | }, 243 | .AddressBlock => { 244 | var cur_periph = &dev.peripherals.items[dev.peripherals.items.len - 1]; 245 | var address_block = &cur_periph.address_block.?; 246 | if (ascii.eqlIgnoreCase(chunk.tag, "/addressBlock")) { 247 | state = .Peripheral; 248 | } else if (ascii.eqlIgnoreCase(chunk.tag, "offset")) { 249 | if (chunk.data) |data| { 250 | address_block.offset = parseHexLiteral(data); 251 | } 252 | } else if (ascii.eqlIgnoreCase(chunk.tag, "size")) { 253 | if (chunk.data) |data| { 254 | address_block.size = parseHexLiteral(data); 255 | } 256 | } else if (ascii.eqlIgnoreCase(chunk.tag, "usage")) { 257 | if (chunk.data) |data| { 258 | try address_block.usage.insertSlice(0, data); 259 | } 260 | } 261 | }, 262 | .Interrupt => { 263 | if (ascii.eqlIgnoreCase(chunk.tag, "/interrupt")) { 264 | if (cur_interrupt.value) |value| { 265 | // If we find a duplicate interrupt, deinit the old one 266 | if (try dev.interrupts.fetchPut(value, cur_interrupt)) |old_entry| { 267 | var old_interrupt = old_entry.value; 268 | var old_name = old_interrupt.name.items; 269 | var cur_name = cur_interrupt.name.items; 270 | if (!mem.eql(u8, old_name, cur_name)) { 271 | warn( 272 | \\ Found duplicate interrupt values with different names: {s} and {s} 273 | \\ The latter will be discarded. 274 | \\ 275 | , .{ 276 | cur_name, 277 | old_name, 278 | }); 279 | } 280 | old_interrupt.deinit(); 281 | } 282 | } 283 | state = .Peripheral; 284 | } else if (ascii.eqlIgnoreCase(chunk.tag, "name")) { 285 | if (chunk.data) |data| { 286 | try cur_interrupt.name.insertSlice(0, data); 287 | } 288 | } else if (ascii.eqlIgnoreCase(chunk.tag, "description")) { 289 | if (chunk.data) |data| { 290 | try cur_interrupt.description.insertSlice(0, data); 291 | } 292 | } else if (ascii.eqlIgnoreCase(chunk.tag, "value")) { 293 | if (chunk.data) |data| { 294 | cur_interrupt.value = fmt.parseInt(u32, data, 10) catch null; 295 | } 296 | } 297 | }, 298 | .Registers => { 299 | var cur_periph = &dev.peripherals.items[dev.peripherals.items.len - 1]; 300 | if (ascii.eqlIgnoreCase(chunk.tag, "/registers")) { 301 | state = .Peripheral; 302 | } else if (ascii.eqlIgnoreCase(chunk.tag, "register")) { 303 | const reset_value = dev.reg_default_reset_value orelse 0; 304 | const size = dev.reg_default_size orelse 32; 305 | var register = try svd.Register.init(allocator, cur_periph.name.items, reset_value, size); 306 | try cur_periph.registers.append(register); 307 | state = .Register; 308 | } 309 | }, 310 | .Register => { 311 | var cur_periph = &dev.peripherals.items[dev.peripherals.items.len - 1]; 312 | var cur_reg = &cur_periph.registers.items[cur_periph.registers.items.len - 1]; 313 | if (ascii.eqlIgnoreCase(chunk.tag, "/register")) { 314 | state = .Registers; 315 | } else if (ascii.eqlIgnoreCase(chunk.tag, "name")) { 316 | if (chunk.data) |data| { 317 | try cur_reg.name.insertSlice(0, data); 318 | } 319 | } else if (ascii.eqlIgnoreCase(chunk.tag, "displayName")) { 320 | if (chunk.data) |data| { 321 | try cur_reg.display_name.insertSlice(0, data); 322 | } 323 | } else if (ascii.eqlIgnoreCase(chunk.tag, "description")) { 324 | if (chunk.data) |data| { 325 | try cur_reg.description.insertSlice(0, data); 326 | } 327 | } else if (ascii.eqlIgnoreCase(chunk.tag, "addressOffset")) { 328 | if (chunk.data) |data| { 329 | cur_reg.address_offset = parseHexLiteral(data); 330 | } 331 | } else if (ascii.eqlIgnoreCase(chunk.tag, "size")) { 332 | if (chunk.data) |data| { 333 | cur_reg.size = parseHexLiteral(data) orelse cur_reg.size; 334 | } 335 | } else if (ascii.eqlIgnoreCase(chunk.tag, "access")) { 336 | if (chunk.data) |data| { 337 | cur_reg.access = parseAccessValue(data) orelse cur_reg.access; 338 | } 339 | } else if (ascii.eqlIgnoreCase(chunk.tag, "resetValue")) { 340 | if (chunk.data) |data| { 341 | cur_reg.reset_value = parseHexLiteral(data) orelse cur_reg.reset_value; // TODO: test orelse break 342 | } 343 | } else if (ascii.eqlIgnoreCase(chunk.tag, "fields")) { 344 | state = .Fields; 345 | } 346 | }, 347 | .Fields => { 348 | var cur_periph = &dev.peripherals.items[dev.peripherals.items.len - 1]; 349 | var cur_reg = &cur_periph.registers.items[cur_periph.registers.items.len - 1]; 350 | if (ascii.eqlIgnoreCase(chunk.tag, "/fields")) { 351 | state = .Register; 352 | } else if (ascii.eqlIgnoreCase(chunk.tag, "field")) { 353 | var field = try svd.Field.init(allocator, cur_periph.name.items, cur_reg.name.items, cur_reg.reset_value); 354 | try cur_reg.fields.append(field); 355 | state = .Field; 356 | } 357 | }, 358 | .Field => { 359 | var cur_periph = &dev.peripherals.items[dev.peripherals.items.len - 1]; 360 | var cur_reg = &cur_periph.registers.items[cur_periph.registers.items.len - 1]; 361 | var cur_field = &cur_reg.fields.items[cur_reg.fields.items.len - 1]; 362 | if (ascii.eqlIgnoreCase(chunk.tag, "/field")) { 363 | state = .Fields; 364 | } else if (ascii.eqlIgnoreCase(chunk.tag, "name")) { 365 | if (chunk.data) |data| { 366 | try cur_field.name.insertSlice(0, data); 367 | } 368 | } else if (ascii.eqlIgnoreCase(chunk.tag, "description")) { 369 | if (chunk.data) |data| { 370 | try cur_field.description.insertSlice(0, data); 371 | } 372 | } else if (ascii.eqlIgnoreCase(chunk.tag, "bitOffset")) { 373 | if (chunk.data) |data| { 374 | cur_field.bit_offset = fmt.parseInt(u32, data, 10) catch null; 375 | } 376 | } else if (ascii.eqlIgnoreCase(chunk.tag, "bitWidth")) { 377 | if (chunk.data) |data| { 378 | cur_field.bit_width = fmt.parseInt(u32, data, 10) catch null; 379 | } 380 | } else if (ascii.eqlIgnoreCase(chunk.tag, "access")) { 381 | if (chunk.data) |data| { 382 | cur_field.access = parseAccessValue(data) orelse cur_field.access; 383 | } 384 | } 385 | }, 386 | .Finished => { 387 | // wait for EOF 388 | }, 389 | } 390 | } 391 | if (state == .Finished) { 392 | try std.io.getStdOut().writer().print("{s}\n", .{register_def}); 393 | try std.io.getStdOut().writer().print("{}\n", .{dev}); 394 | } else { 395 | return error.InvalidXML; 396 | } 397 | } 398 | 399 | const SvdParseState = enum { 400 | Device, 401 | Cpu, 402 | Peripherals, 403 | Peripheral, 404 | AddressBlock, 405 | Interrupt, 406 | Registers, 407 | Register, 408 | Fields, 409 | Field, 410 | Finished, 411 | }; 412 | 413 | const XmlChunk = struct { 414 | tag: []const u8, 415 | data: ?[]const u8, 416 | derivedFrom: ?[]const u8, 417 | }; 418 | 419 | fn getChunk(line: []const u8) ?XmlChunk { 420 | var chunk = XmlChunk{ 421 | .tag = undefined, 422 | .data = null, 423 | .derivedFrom = null, 424 | }; 425 | 426 | var trimmed = mem.trim(u8, line, " \n"); 427 | var toker = mem.tokenize(u8, trimmed, "<>"); //" =\n<>\""); 428 | 429 | if (toker.next()) |maybe_tag| { 430 | var tag_toker = mem.tokenize(u8, maybe_tag, " =\""); 431 | chunk.tag = tag_toker.next() orelse return null; 432 | if (tag_toker.next()) |maybe_tag_property| { 433 | if (ascii.eqlIgnoreCase(maybe_tag_property, "derivedFrom")) { 434 | chunk.derivedFrom = tag_toker.next(); 435 | } 436 | } 437 | } else { 438 | return null; 439 | } 440 | 441 | if (toker.next()) |chunk_data| { 442 | chunk.data = chunk_data; 443 | } 444 | 445 | return chunk; 446 | } 447 | 448 | test "getChunk" { 449 | const valid_xml = " STM32F7x7 \n"; 450 | const expected_chunk = XmlChunk{ .tag = "name", .data = "STM32F7x7", .derivedFrom = null }; 451 | 452 | const chunk = getChunk(valid_xml).?; 453 | std.testing.expectEqualSlices(u8, chunk.tag, expected_chunk.tag); 454 | std.testing.expectEqualSlices(u8, chunk.data.?, expected_chunk.data.?); 455 | 456 | const no_data_xml = " \n"; 457 | const expected_no_data_chunk = XmlChunk{ .tag = "name", .data = null, .derivedFrom = null }; 458 | const no_data_chunk = getChunk(no_data_xml).?; 459 | std.testing.expectEqualSlices(u8, no_data_chunk.tag, expected_no_data_chunk.tag); 460 | std.testing.expectEqual(no_data_chunk.data, expected_no_data_chunk.data); 461 | 462 | const comments_xml = "Auxiliary Cache Control register"; 463 | const expected_comments_chunk = XmlChunk{ .tag = "description", .data = "Auxiliary Cache Control register", .derivedFrom = null }; 464 | const comments_chunk = getChunk(comments_xml).?; 465 | std.testing.expectEqualSlices(u8, comments_chunk.tag, expected_comments_chunk.tag); 466 | std.testing.expectEqualSlices(u8, comments_chunk.data.?, expected_comments_chunk.data.?); 467 | 468 | const derived = " "; 469 | const expected_derived_chunk = XmlChunk{ .tag = "peripheral", .data = null, .derivedFrom = "TIM10" }; 470 | const derived_chunk = getChunk(derived).?; 471 | std.testing.expectEqualSlices(u8, derived_chunk.tag, expected_derived_chunk.tag); 472 | std.testing.expectEqualSlices(u8, derived_chunk.derivedFrom.?, expected_derived_chunk.derivedFrom.?); 473 | std.testing.expectEqual(derived_chunk.data, expected_derived_chunk.data); 474 | } 475 | 476 | fn textToBool(data: []const u8) ?bool { 477 | if (ascii.eqlIgnoreCase(data, "true")) { 478 | return true; 479 | } else if (ascii.eqlIgnoreCase(data, "false")) { 480 | return false; 481 | } else { 482 | return null; 483 | } 484 | } 485 | 486 | fn parseHexLiteral(data: []const u8) ?u32 { 487 | if (data.len <= 2) return null; 488 | return fmt.parseInt(u32, data[2..], 16) catch null; 489 | } 490 | 491 | fn parseAccessValue(data: []const u8) ?svd.Access { 492 | if (ascii.eqlIgnoreCase(data, "read-write")) { 493 | return .ReadWrite; 494 | } else if (ascii.eqlIgnoreCase(data, "read-only")) { 495 | return .ReadOnly; 496 | } else if (ascii.eqlIgnoreCase(data, "write-only")) { 497 | return .WriteOnly; 498 | } 499 | return null; 500 | } 501 | -------------------------------------------------------------------------------- /src/svd.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const ArrayList = std.ArrayList; 4 | const Allocator = std.mem.Allocator; 5 | const AutoHashMap = std.AutoHashMap; 6 | const min = std.math.min; 7 | const warn = std.debug.warn; 8 | 9 | /// Top Level 10 | pub const Device = struct { 11 | name: ArrayList(u8), 12 | version: ArrayList(u8), 13 | description: ArrayList(u8), 14 | cpu: ?Cpu, 15 | 16 | /// Bus Interface Properties 17 | /// Smallest addressable unit in bits 18 | address_unit_bits: ?u32, 19 | 20 | /// The Maximum data bit width accessible within a single transfer 21 | max_bit_width: ?u32, 22 | 23 | /// Start register default properties 24 | reg_default_size: ?u32, 25 | reg_default_reset_value: ?u32, 26 | reg_default_reset_mask: ?u32, 27 | peripherals: Peripherals, 28 | interrupts: Interrupts, 29 | 30 | const Self = @This(); 31 | 32 | pub fn init(allocator: Allocator) !Self { 33 | var name = ArrayList(u8).init(allocator); 34 | errdefer name.deinit(); 35 | var version = ArrayList(u8).init(allocator); 36 | errdefer version.deinit(); 37 | var description = ArrayList(u8).init(allocator); 38 | errdefer description.deinit(); 39 | var peripherals = Peripherals.init(allocator); 40 | errdefer peripherals.deinit(); 41 | var interrupts = Interrupts.init(allocator); 42 | errdefer interrupts.deinit(); 43 | 44 | return Self{ 45 | .name = name, 46 | .version = version, 47 | .description = description, 48 | .cpu = null, 49 | .address_unit_bits = null, 50 | .max_bit_width = null, 51 | .reg_default_size = null, 52 | .reg_default_reset_value = null, 53 | .reg_default_reset_mask = null, 54 | .peripherals = peripherals, 55 | .interrupts = interrupts, 56 | }; 57 | } 58 | 59 | pub fn deinit(self: *Self) void { 60 | self.name.deinit(); 61 | self.version.deinit(); 62 | self.description.deinit(); 63 | self.peripherals.deinit(); 64 | self.interrupts.deinit(); 65 | } 66 | 67 | pub fn format(self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, out_stream: anytype) !void { 68 | const name = if (self.name.items.len == 0) "unknown" else self.name.items; 69 | const version = if (self.version.items.len == 0) "unknown" else self.version.items; 70 | const description = if (self.description.items.len == 0) "unknown" else self.description.items; 71 | 72 | try out_stream.print( 73 | \\pub const device_name = "{s}"; 74 | \\pub const device_revision = "{s}"; 75 | \\pub const device_description = "{s}"; 76 | \\ 77 | , .{ name, version, description }); 78 | if (self.cpu) |the_cpu| { 79 | try out_stream.print("{}\n", .{the_cpu}); 80 | } 81 | // now print peripherals 82 | for (self.peripherals.items) |peripheral| { 83 | try out_stream.print("{}\n", .{peripheral}); 84 | } 85 | // now print interrupt table 86 | try out_stream.writeAll("pub const interrupts = struct {\n"); 87 | var iter = self.interrupts.iterator(); 88 | while (iter.next()) |entry| { 89 | var interrupt = entry.value_ptr.*; 90 | if (interrupt.value) |int_value| { 91 | try out_stream.print( 92 | "pub const {s} = {};\n", 93 | .{ interrupt.name.items, int_value }, 94 | ); 95 | } 96 | } 97 | try out_stream.writeAll("};"); 98 | return; 99 | } 100 | }; 101 | 102 | pub const Cpu = struct { 103 | name: ArrayList(u8), 104 | revision: ArrayList(u8), 105 | endian: ArrayList(u8), 106 | mpu_present: ?bool, 107 | fpu_present: ?bool, 108 | nvic_prio_bits: ?u32, 109 | vendor_systick_config: ?bool, 110 | 111 | const Self = @This(); 112 | 113 | pub fn init(allocator: Allocator) !Self { 114 | var name = ArrayList(u8).init(allocator); 115 | errdefer name.deinit(); 116 | var revision = ArrayList(u8).init(allocator); 117 | errdefer revision.deinit(); 118 | var endian = ArrayList(u8).init(allocator); 119 | errdefer endian.deinit(); 120 | 121 | return Self{ 122 | .name = name, 123 | .revision = revision, 124 | .endian = endian, 125 | .mpu_present = null, 126 | .fpu_present = null, 127 | .nvic_prio_bits = null, 128 | .vendor_systick_config = null, 129 | }; 130 | } 131 | 132 | pub fn deinit(self: *Self) void { 133 | self.name.deinit(); 134 | self.revision.deinit(); 135 | self.endian.deinit(); 136 | } 137 | 138 | pub fn format(self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, out_stream: anytype) !void { 139 | try out_stream.writeAll("\n"); 140 | 141 | const name = if (self.name.items.len == 0) "unknown" else self.name.items; 142 | const revision = if (self.revision.items.len == 0) "unknown" else self.revision.items; 143 | const endian = if (self.endian.items.len == 0) "unknown" else self.endian.items; 144 | const mpu_present = self.mpu_present orelse false; 145 | const fpu_present = self.mpu_present orelse false; 146 | const vendor_systick_config = self.vendor_systick_config orelse false; 147 | try out_stream.print( 148 | \\pub const cpu = struct {{ 149 | \\ pub const name = "{s}"; 150 | \\ pub const revision = "{s}"; 151 | \\ pub const endian = "{s}"; 152 | \\ pub const mpu_present = {}; 153 | \\ pub const fpu_present = {}; 154 | \\ pub const vendor_systick_config = {}; 155 | \\ 156 | , .{ name, revision, endian, mpu_present, fpu_present, vendor_systick_config }); 157 | if (self.nvic_prio_bits) |prio_bits| { 158 | try out_stream.print( 159 | \\ pub const nvic_prio_bits = {}; 160 | \\ 161 | , .{prio_bits}); 162 | } 163 | try out_stream.writeAll("};"); 164 | return; 165 | } 166 | }; 167 | 168 | pub const Peripherals = ArrayList(Peripheral); 169 | 170 | pub const Peripheral = struct { 171 | name: ArrayList(u8), 172 | group_name: ArrayList(u8), 173 | description: ArrayList(u8), 174 | base_address: ?u32, 175 | address_block: ?AddressBlock, 176 | registers: Registers, 177 | 178 | const Self = @This(); 179 | 180 | pub fn init(allocator: Allocator) !Self { 181 | var name = ArrayList(u8).init(allocator); 182 | errdefer name.deinit(); 183 | var group_name = ArrayList(u8).init(allocator); 184 | errdefer group_name.deinit(); 185 | var description = ArrayList(u8).init(allocator); 186 | errdefer description.deinit(); 187 | var registers = Registers.init(allocator); 188 | errdefer registers.deinit(); 189 | 190 | return Self{ 191 | .name = name, 192 | .group_name = group_name, 193 | .description = description, 194 | .base_address = null, 195 | .address_block = null, 196 | .registers = registers, 197 | }; 198 | } 199 | 200 | pub fn copy(self: Self, allocator: Allocator) !Self { 201 | var the_copy = try Self.init(allocator); 202 | errdefer the_copy.deinit(); 203 | 204 | try the_copy.name.appendSlice(self.name.items); 205 | try the_copy.group_name.appendSlice(self.group_name.items); 206 | try the_copy.description.appendSlice(self.description.items); 207 | the_copy.base_address = self.base_address; 208 | the_copy.address_block = self.address_block; 209 | for (self.registers.items) |self_register| { 210 | try the_copy.registers.append(try self_register.copy(allocator)); 211 | } 212 | 213 | return the_copy; 214 | } 215 | 216 | pub fn deinit(self: *Self) void { 217 | self.name.deinit(); 218 | self.group_name.deinit(); 219 | self.description.deinit(); 220 | self.registers.deinit(); 221 | } 222 | 223 | pub fn isValid(self: Self) bool { 224 | if (self.name.items.len == 0) { 225 | return false; 226 | } 227 | _ = self.base_address orelse return false; 228 | 229 | return true; 230 | } 231 | 232 | pub fn format(self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, out_stream: anytype) !void { 233 | try out_stream.writeAll("\n"); 234 | if (!self.isValid()) { 235 | try out_stream.writeAll("// Not enough info to print peripheral value\n"); 236 | return; 237 | } 238 | const name = self.name.items; 239 | const description = if (self.description.items.len == 0) "No description" else self.description.items; 240 | const base_address = self.base_address.?; 241 | try out_stream.print( 242 | \\/// {s} 243 | \\pub const {s} = struct {{ 244 | \\ 245 | \\const base_address = 0x{x}; 246 | , .{ description, name, base_address }); 247 | // now print registers 248 | for (self.registers.items) |register| { 249 | try out_stream.print("{}\n", .{register}); 250 | } 251 | // and close the peripheral 252 | try out_stream.print("}};", .{}); 253 | 254 | return; 255 | } 256 | }; 257 | 258 | pub const AddressBlock = struct { 259 | offset: ?u32, 260 | size: ?u32, 261 | usage: ArrayList(u8), 262 | 263 | const Self = @This(); 264 | 265 | pub fn init(allocator: Allocator) !Self { 266 | var usage = ArrayList(u8).init(allocator); 267 | errdefer usage.deinit(); 268 | 269 | return Self{ 270 | .offset = null, 271 | .size = null, 272 | .usage = usage, 273 | }; 274 | } 275 | 276 | pub fn deinit(self: *Self) void { 277 | self.usage.deinit(); 278 | } 279 | }; 280 | 281 | pub const Interrupts = AutoHashMap(u32, Interrupt); 282 | 283 | pub const Interrupt = struct { 284 | name: ArrayList(u8), 285 | description: ArrayList(u8), 286 | value: ?u32, 287 | 288 | const Self = @This(); 289 | 290 | pub fn init(allocator: Allocator) !Self { 291 | var name = ArrayList(u8).init(allocator); 292 | errdefer name.deinit(); 293 | var description = ArrayList(u8).init(allocator); 294 | errdefer description.deinit(); 295 | 296 | return Self{ 297 | .name = name, 298 | .description = description, 299 | .value = null, 300 | }; 301 | } 302 | 303 | pub fn copy(self: Self, allocator: Allocator) !Self { 304 | var the_copy = try Self.init(allocator); 305 | 306 | try the_copy.name.append(self.name.items); 307 | try the_copy.description.append(self.description.items); 308 | the_copy.value = self.value; 309 | 310 | return the_copy; 311 | } 312 | 313 | pub fn deinit(self: *Self) void { 314 | self.name.deinit(); 315 | self.description.deinit(); 316 | } 317 | 318 | pub fn isValid(self: Self) bool { 319 | if (self.name.items.len == 0) { 320 | return false; 321 | } 322 | _ = self.value orelse return false; 323 | 324 | return true; 325 | } 326 | 327 | pub fn format(self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, out_stream: anytype) !void { 328 | try out_stream.writeAll("\n"); 329 | if (!self.isValid()) { 330 | try out_stream.writeAll("// Not enough info to print interrupt value\n"); 331 | return; 332 | } 333 | const name = self.name.items; 334 | const description = if (self.description.items.len == 0) "No description" else self.description.items; 335 | try out_stream.print( 336 | \\/// {s} 337 | \\pub const {s} = {s}; 338 | \\ 339 | , .{ description, name, self.value.? }); 340 | } 341 | }; 342 | 343 | const Registers = ArrayList(Register); 344 | 345 | pub const Register = struct { 346 | periph_containing: ArrayList(u8), 347 | name: ArrayList(u8), 348 | display_name: ArrayList(u8), 349 | description: ArrayList(u8), 350 | address_offset: ?u32, 351 | size: u32, 352 | reset_value: u32, 353 | fields: Fields, 354 | 355 | access: Access = .ReadWrite, 356 | 357 | const Self = @This(); 358 | 359 | pub fn init(allocator: Allocator, periph: []const u8, reset_value: u32, size: u32) !Self { 360 | var prefix = ArrayList(u8).init(allocator); 361 | errdefer prefix.deinit(); 362 | try prefix.appendSlice(periph); 363 | var name = ArrayList(u8).init(allocator); 364 | errdefer name.deinit(); 365 | var display_name = ArrayList(u8).init(allocator); 366 | errdefer display_name.deinit(); 367 | var description = ArrayList(u8).init(allocator); 368 | errdefer description.deinit(); 369 | var fields = Fields.init(allocator); 370 | errdefer fields.deinit(); 371 | 372 | return Self{ 373 | .periph_containing = prefix, 374 | .name = name, 375 | .display_name = display_name, 376 | .description = description, 377 | .address_offset = null, 378 | .size = size, 379 | .reset_value = reset_value, 380 | .fields = fields, 381 | }; 382 | } 383 | 384 | pub fn copy(self: Self, allocator: Allocator) !Self { 385 | var the_copy = try Self.init(allocator, self.periph_containing.items, self.reset_value, self.size); 386 | 387 | try the_copy.name.appendSlice(self.name.items); 388 | try the_copy.display_name.appendSlice(self.display_name.items); 389 | try the_copy.description.appendSlice(self.description.items); 390 | the_copy.address_offset = self.address_offset; 391 | the_copy.access = self.access; 392 | for (self.fields.items) |self_field| { 393 | try the_copy.fields.append(try self_field.copy(allocator)); 394 | } 395 | 396 | return the_copy; 397 | } 398 | 399 | pub fn deinit(self: *Self) void { 400 | self.periph_containing.deinit(); 401 | self.name.deinit(); 402 | self.display_name.deinit(); 403 | self.description.deinit(); 404 | 405 | self.fields.deinit(); 406 | } 407 | 408 | pub fn isValid(self: Self) bool { 409 | if (self.name.items.len == 0) { 410 | return false; 411 | } 412 | _ = self.address_offset orelse return false; 413 | 414 | return true; 415 | } 416 | 417 | fn fieldsSortCompare(_: void, left: Field, right: Field) bool { 418 | if (left.bit_offset != null and right.bit_offset != null) { 419 | if (left.bit_offset.? < right.bit_offset.?) { 420 | return true; 421 | } 422 | if (left.bit_offset.? > right.bit_offset.?) { 423 | return false; 424 | } 425 | } else if (left.bit_offset == null) { 426 | return true; 427 | } 428 | 429 | return false; 430 | } 431 | 432 | fn alignedEndOfUnusedChunk(chunk_start: u32, last_unused: u32) u32 { 433 | // Next multiple of 8 from chunk_start + 1 434 | const next_multiple = (chunk_start + 8) & ~@as(u32, 7); 435 | return min(next_multiple, last_unused); 436 | } 437 | 438 | fn writeUnusedField(first_unused: u32, last_unused: u32, reg_reset_value: u32, out_stream: anytype) !void { 439 | // Fill unused bits between two fields 440 | // TODO: right now we have to manually chunk unused bits to 8-bit boundaries as a workaround 441 | // to this bug https://github.com/ziglang/zig/issues/2627 442 | var chunk_start = first_unused; 443 | var chunk_end = alignedEndOfUnusedChunk(chunk_start, last_unused); 444 | try out_stream.print("\n/// unused [{}:{}]", .{ first_unused, last_unused - 1 }); 445 | while (chunk_start < last_unused) : ({ 446 | chunk_start = chunk_end; 447 | chunk_end = alignedEndOfUnusedChunk(chunk_start, last_unused); 448 | }) { 449 | try out_stream.writeAll("\n"); 450 | const chunk_width = chunk_end - chunk_start; 451 | const unused_value = Field.fieldResetValue(chunk_start, chunk_width, reg_reset_value); 452 | 453 | try out_stream.print("_unused{}: u{} = {},", .{ chunk_start, chunk_width, unused_value }); 454 | } 455 | } 456 | 457 | pub fn format(self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, out_stream: anytype) !void { 458 | try out_stream.writeAll("\n"); 459 | if (!self.isValid()) { 460 | try out_stream.writeAll("// Not enough info to print register value\n"); 461 | return; 462 | } 463 | const name = self.name.items; 464 | // const periph = self.periph_containing.items; 465 | const description = if (self.description.items.len == 0) "No description" else self.description.items; 466 | // print packed struct containing fields 467 | try out_stream.print( 468 | \\/// {s} 469 | \\const {s}_val = packed struct {{ 470 | , .{ name, name }); 471 | 472 | // Sort fields from LSB to MSB for next step 473 | std.sort.sort(Field, self.fields.items, {}, fieldsSortCompare); 474 | 475 | var last_uncovered_bit: u32 = 0; 476 | for (self.fields.items) |field| { 477 | if ((field.bit_offset == null) or (field.bit_width == null)) { 478 | try out_stream.writeAll("// Not enough info to print register\n"); 479 | return; 480 | } 481 | 482 | const bit_offset = field.bit_offset.?; 483 | const bit_width = field.bit_width.?; 484 | if (last_uncovered_bit != bit_offset) { 485 | try writeUnusedField(last_uncovered_bit, bit_offset, self.reset_value, out_stream); 486 | } 487 | try out_stream.print("{}", .{field}); 488 | last_uncovered_bit = bit_offset + bit_width; 489 | } 490 | 491 | // Check if we need padding at the end 492 | if (last_uncovered_bit != 32) { 493 | try writeUnusedField(last_uncovered_bit, 32, self.reset_value, out_stream); 494 | } 495 | 496 | // close the struct and init the register 497 | try out_stream.print( 498 | \\ 499 | \\}}; 500 | \\/// {s} 501 | \\pub const {s} = Register({s}_val).init(base_address + 0x{x}); 502 | , .{ description, name, name, self.address_offset.? }); 503 | 504 | return; 505 | } 506 | }; 507 | 508 | pub const Access = enum { 509 | ReadOnly, 510 | WriteOnly, 511 | ReadWrite, 512 | }; 513 | 514 | pub const Fields = ArrayList(Field); 515 | 516 | pub const Field = struct { 517 | periph: ArrayList(u8), 518 | register: ArrayList(u8), 519 | register_reset_value: u32, 520 | name: ArrayList(u8), 521 | description: ArrayList(u8), 522 | bit_offset: ?u32, 523 | bit_width: ?u32, 524 | 525 | access: Access = .ReadWrite, 526 | 527 | const Self = @This(); 528 | 529 | pub fn init(allocator: Allocator, periph_containing: []const u8, register_containing: []const u8, register_reset_value: u32) !Self { 530 | var periph = ArrayList(u8).init(allocator); 531 | try periph.appendSlice(periph_containing); 532 | errdefer periph.deinit(); 533 | var register = ArrayList(u8).init(allocator); 534 | try register.appendSlice(register_containing); 535 | errdefer register.deinit(); 536 | var name = ArrayList(u8).init(allocator); 537 | errdefer name.deinit(); 538 | var description = ArrayList(u8).init(allocator); 539 | errdefer description.deinit(); 540 | 541 | return Self{ 542 | .periph = periph, 543 | .register = register, 544 | .register_reset_value = register_reset_value, 545 | .name = name, 546 | .description = description, 547 | .bit_offset = null, 548 | .bit_width = null, 549 | }; 550 | } 551 | 552 | pub fn copy(self: Self, allocator: Allocator) !Self { 553 | var the_copy = try Self.init(allocator, self.periph.items, self.register.items, self.register_reset_value); 554 | 555 | try the_copy.name.appendSlice(self.name.items); 556 | try the_copy.description.appendSlice(self.description.items); 557 | the_copy.bit_offset = self.bit_offset; 558 | the_copy.bit_width = self.bit_width; 559 | the_copy.access = self.access; 560 | 561 | return the_copy; 562 | } 563 | 564 | pub fn deinit(self: *Self) void { 565 | self.periph.deinit(); 566 | self.register.deinit(); 567 | self.name.deinit(); 568 | self.description.deinit(); 569 | } 570 | 571 | pub fn fieldResetValue(bit_start: u32, bit_width: u32, reg_reset_value: u32) u32 { 572 | const shifted_reset_value = reg_reset_value >> @intCast(u5, bit_start); 573 | const reset_value_mask = @intCast(u32, (@as(u33, 1) << @intCast(u6, bit_width)) - 1); 574 | 575 | return shifted_reset_value & reset_value_mask; 576 | } 577 | 578 | pub fn format(self: Self, comptime _: []const u8, _: std.fmt.FormatOptions, out_stream: anytype) !void { 579 | try out_stream.writeAll("\n"); 580 | if (self.name.items.len == 0) { 581 | try out_stream.writeAll("// No name to print field value\n"); 582 | return; 583 | } 584 | if ((self.bit_offset == null) or (self.bit_width == null)) { 585 | try out_stream.writeAll("// Not enough info to print field\n"); 586 | return; 587 | } 588 | const name = self.name.items; 589 | const description = if (self.description.items.len == 0) "No description" else self.description.items; 590 | const start_bit = self.bit_offset.?; 591 | const end_bit = (start_bit + self.bit_width.? - 1); 592 | const bit_width = self.bit_width.?; 593 | const reg_reset_value = self.register_reset_value; 594 | const reset_value = fieldResetValue(start_bit, bit_width, reg_reset_value); 595 | try out_stream.print( 596 | \\/// {s} [{}:{}] 597 | \\/// {s} 598 | \\{s}: u{} = {}, 599 | , .{ 600 | name, 601 | start_bit, 602 | end_bit, 603 | // description 604 | description, 605 | // val 606 | name, 607 | bit_width, 608 | reset_value, 609 | }); 610 | return; 611 | } 612 | }; 613 | 614 | test "Field print" { 615 | var allocator = std.testing.allocator; 616 | const fieldDesiredPrint = 617 | \\ 618 | \\/// RNGEN [2:2] 619 | \\/// RNGEN comment 620 | \\RNGEN: u1 = 1, 621 | \\ 622 | ; 623 | 624 | var output_buffer = ArrayList(u8).init(allocator); 625 | defer output_buffer.deinit(); 626 | var buf_stream = output_buffer.writer(); 627 | 628 | var field = try Field.init(allocator, "PERIPH", "RND", 0b101); 629 | defer field.deinit(); 630 | 631 | try field.name.appendSlice("RNGEN"); 632 | try field.description.appendSlice("RNGEN comment"); 633 | field.bit_offset = 2; 634 | field.bit_width = 1; 635 | 636 | try buf_stream.print("{}\n", .{field}); 637 | std.testing.expect(std.mem.eql(u8, output_buffer.items, fieldDesiredPrint)); 638 | } 639 | 640 | test "Register Print" { 641 | var allocator = std.testing.allocator; 642 | const registerDesiredPrint = 643 | \\ 644 | \\/// RND 645 | \\const RND_val = packed struct { 646 | \\/// unused [0:1] 647 | \\_unused0: u2 = 1, 648 | \\/// RNGEN [2:2] 649 | \\/// RNGEN comment 650 | \\RNGEN: u1 = 1, 651 | \\/// unused [3:9] 652 | \\_unused3: u5 = 0, 653 | \\_unused8: u2 = 0, 654 | \\/// SEED [10:12] 655 | \\/// SEED comment 656 | \\SEED: u3 = 0, 657 | \\/// unused [13:31] 658 | \\_unused13: u3 = 0, 659 | \\_unused16: u8 = 0, 660 | \\_unused24: u8 = 0, 661 | \\}; 662 | \\/// RND comment 663 | \\pub const RND = Register(RND_val).init(base_address + 0x100); 664 | \\ 665 | ; 666 | 667 | var output_buffer = ArrayList(u8).init(allocator); 668 | defer output_buffer.deinit(); 669 | var buf_stream = output_buffer.writer(); 670 | 671 | var register = try Register.init(allocator, "PERIPH", 0b101, 0x20); 672 | defer register.deinit(); 673 | try register.name.appendSlice("RND"); 674 | try register.description.appendSlice("RND comment"); 675 | register.address_offset = 0x100; 676 | register.size = 0x20; 677 | 678 | var field = try Field.init(allocator, "PERIPH", "RND", 0b101); 679 | defer field.deinit(); 680 | 681 | try field.name.appendSlice("RNGEN"); 682 | try field.description.appendSlice("RNGEN comment"); 683 | field.bit_offset = 2; 684 | field.bit_width = 1; 685 | field.access = .ReadWrite; // write field will exist 686 | 687 | var field2 = try Field.init(allocator, "PERIPH", "RND", 0b101); 688 | defer field2.deinit(); 689 | 690 | try field2.name.appendSlice("SEED"); 691 | try field2.description.appendSlice("SEED comment"); 692 | field2.bit_offset = 10; 693 | field2.bit_width = 3; 694 | field2.access = .ReadWrite; 695 | 696 | try register.fields.append(field); 697 | try register.fields.append(field2); 698 | 699 | try buf_stream.print("{}\n", .{register}); 700 | std.testing.expectEqualSlices(u8, output_buffer.items, registerDesiredPrint); 701 | } 702 | 703 | test "Peripheral Print" { 704 | var allocator = std.testing.allocator; 705 | const peripheralDesiredPrint = 706 | \\ 707 | \\/// PERIPH comment 708 | \\pub const PERIPH = struct { 709 | \\ 710 | \\const base_address = 0x24000; 711 | \\/// RND 712 | \\const RND_val = packed struct { 713 | \\/// unused [0:1] 714 | \\_unused0: u2 = 1, 715 | \\/// RNGEN [2:2] 716 | \\/// RNGEN comment 717 | \\RNGEN: u1 = 1, 718 | \\/// unused [3:9] 719 | \\_unused3: u5 = 0, 720 | \\_unused8: u2 = 0, 721 | \\/// SEED [10:12] 722 | \\/// SEED comment 723 | \\SEED: u3 = 0, 724 | \\/// unused [13:31] 725 | \\_unused13: u3 = 0, 726 | \\_unused16: u8 = 0, 727 | \\_unused24: u8 = 0, 728 | \\}; 729 | \\/// RND comment 730 | \\pub const RND = Register(RND_val).init(base_address + 0x100); 731 | \\}; 732 | \\ 733 | ; 734 | 735 | var output_buffer = ArrayList(u8).init(allocator); 736 | defer output_buffer.deinit(); 737 | var buf_stream = output_buffer.writer(); 738 | 739 | var peripheral = try Peripheral.init(allocator); 740 | defer peripheral.deinit(); 741 | try peripheral.name.appendSlice("PERIPH"); 742 | try peripheral.description.appendSlice("PERIPH comment"); 743 | peripheral.base_address = 0x24000; 744 | 745 | var register = try Register.init(allocator, "PERIPH", 0b101, 0x20); 746 | defer register.deinit(); 747 | try register.name.appendSlice("RND"); 748 | try register.description.appendSlice("RND comment"); 749 | register.address_offset = 0x100; 750 | register.size = 0x20; 751 | 752 | var field = try Field.init(allocator, "PERIPH", "RND", 0b101); 753 | defer field.deinit(); 754 | 755 | try field.name.appendSlice("RNGEN"); 756 | try field.description.appendSlice("RNGEN comment"); 757 | field.bit_offset = 2; 758 | field.bit_width = 1; 759 | field.access = .ReadOnly; // since only register, write field will not exist 760 | 761 | var field2 = try Field.init(allocator, "PERIPH", "RND", 0b101); 762 | defer field2.deinit(); 763 | 764 | try field2.name.appendSlice("SEED"); 765 | try field2.description.appendSlice("SEED comment"); 766 | field2.bit_offset = 10; 767 | field2.bit_width = 3; 768 | field2.access = .ReadWrite; 769 | 770 | try register.fields.append(field); 771 | try register.fields.append(field2); 772 | 773 | try peripheral.registers.append(register); 774 | 775 | try buf_stream.print("{}\n", .{peripheral}); 776 | std.testing.expectEqualSlices(u8, peripheralDesiredPrint, output_buffer.items); 777 | } 778 | fn bitWidthToMask(width: u32) u32 { 779 | const max_supported_bits = 32; 780 | const width_to_mask = blk: { 781 | comptime var mask_array: [max_supported_bits + 1]u32 = undefined; 782 | inline for (mask_array) |*item, i| { 783 | const i_use = if (i == 0) max_supported_bits else i; 784 | // This is needed to support both Zig 0.7 and 0.8 785 | const int_type_info = 786 | if (@hasField(builtin.TypeInfo.Int, "signedness")) 787 | .{ .signedness = .unsigned, .bits = i_use } else .{ .is_signed = false, .bits = i_use }; 788 | 789 | item.* = std.math.maxInt(@Type(builtin.TypeInfo{ .Int = int_type_info })); 790 | } 791 | break :blk mask_array; 792 | }; 793 | const width_to_mask_slice = width_to_mask[0..]; 794 | 795 | return width_to_mask_slice[if (width > max_supported_bits) 0 else width]; 796 | } 797 | --------------------------------------------------------------------------------