const std = @import("std"); const Bitfield = @import("bitfield").Bitfield; const Bit = @import("bitfield").Bit; const Bus = @import("Bus.zig"); const SharedCtx = @import("../emu.zig").SharedCtx; const masks = @import("../io.zig").masks; const IntEnable = @import("../io.zig").IntEnable; const IntRequest = @import("../io.zig").IntEnable; const dma = @import("dma.zig"); const sext = @import("../../util.zig").sext; const shift = @import("../../util.zig").shift; const log = std.log.scoped(.nds9_io); pub const Io = struct { const fill_len = 0x10 * @sizeOf(u32); shr: *SharedCtx.Io, /// Interrupt Master Enable /// Read/Write ime: bool = false, /// Interrupt Enable /// Read/Write /// /// Caller must cast the `u32` to either `nds7.IntEnable` or `nds9.IntEnable` ie: IntEnable = .{ .raw = 0x0000_0000 }, /// IF - Interrupt Request /// Read/Write /// /// Caller must cast the `u32` to either `nds7.IntRequest` or `nds9.IntRequest` irq: IntRequest = .{ .raw = 0x0000_0000 }, /// DS Maths div: Divisor = .{}, sqrt: SquareRootUnit = .{}, // TODO: move somewhere else? dma_fill: [fill_len]u8 = [_]u8{0} ** fill_len, pub fn init(io: *SharedCtx.Io) @This() { return .{ .shr = io }; } }; pub fn read(bus: *const Bus, comptime T: type, address: u32) T { return switch (T) { u32 => switch (address) { // DMA Transfers 0x0400_00B0...0x0400_00DC => dma.read(T, &bus.dma, address) orelse 0x0000_0000, 0x0400_00E0...0x0400_00EC => std.mem.readInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], .little), // Timers 0x0400_0100...0x0400_010C => warn("TODO(timer): read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), 0x0400_0180 => bus.io.shr.ipc._nds9.sync.raw, 0x0400_0208 => @intFromBool(bus.io.ime), 0x0400_0210 => bus.io.ie.raw, 0x0400_0214 => bus.io.irq.raw, // zig fmt: off 0x0400_0240 => @as(u32, bus.ppu.vram.io.cnt_d.raw) << 24 | @as(u32, bus.ppu.vram.io.cnt_c.raw) << 16 | @as(u32, bus.ppu.vram.io.cnt_b.raw) << 8 | bus.ppu.vram.io.cnt_a.raw << 0, // zig fmt: on 0x0400_0280 => bus.io.div.cnt.raw, 0x0400_02A0, 0x0400_02A4 => @truncate(bus.io.div.result >> shift(u64, address)), 0x0400_02A8, 0x0400_02AC => @truncate(bus.io.div.remainder >> shift(u64, address)), 0x0400_02B4 => @truncate(bus.io.sqrt.result), 0x0400_1000 => bus.ppu.engines[1].dispcnt.raw, 0x0410_0000 => bus.io.shr.ipc.recv(.nds9), 0x0400_4000, 0x0400_4008 => 0x0000_0000, // Lets software know this is NOT a DSi else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), }, u16 => switch (address) { 0x0400_0006 => bus.ppu.io.nds9.vcount.raw, // DMA Transfers 0x0400_00B0...0x0400_00DE => dma.read(T, &bus.dma, address) orelse 0x0000, 0x0400_00E0...0x0400_00EE => std.mem.readInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], .little), // Timers 0x0400_0100...0x0400_010E => warn("TODO(timer): read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), 0x0400_0004 => bus.ppu.io.nds9.dispstat.raw, 0x0400_0130 => bus.io.shr.input.keyinput().raw, 0x0400_0180 => @truncate(bus.io.shr.ipc._nds9.sync.raw), 0x0400_0184 => @truncate(bus.io.shr.ipc._nds9.cnt.raw), 0x0400_0280 => @truncate(bus.io.div.cnt.raw), 0x0400_02B0 => @truncate(bus.io.sqrt.cnt.raw), 0x0400_1008 => bus.ppu.engines[1].bg[0].cnt.raw, 0x0400_100A => bus.ppu.engines[1].bg[1].cnt.raw, 0x0400_100C => bus.ppu.engines[1].bg[2].cnt.raw, 0x0400_100E => bus.ppu.engines[1].bg[3].cnt.raw, 0x0400_1010 => bus.ppu.engines[1].bg[0].hofs.raw, 0x0400_1012 => bus.ppu.engines[1].bg[0].vofs.raw, 0x0400_1014 => bus.ppu.engines[1].bg[1].hofs.raw, 0x0400_1016 => bus.ppu.engines[1].bg[1].vofs.raw, 0x0400_1018 => bus.ppu.engines[1].bg[2].hofs.raw, 0x0400_101A => bus.ppu.engines[1].bg[2].vofs.raw, 0x0400_101C => bus.ppu.engines[1].bg[3].hofs.raw, 0x0400_101E => bus.ppu.engines[1].bg[3].vofs.raw, else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), }, u8 => switch (address) { // DMA Transfers 0x0400_00B0...0x0400_00DF => dma.read(T, &bus.dma, address) orelse 0x00, 0x0400_00E0...0x0400_00EF => std.mem.readInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], .little), // Timers 0x0400_0100...0x0400_010F => warn("TODO(timer): read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), 0x0400_0208 => @intFromBool(bus.io.ime), 0x0400_4000 => 0x00, // Lets software know this is NOT a DSi else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), }, else => @compileError(T ++ " is an unsupported bus read type"), }; } const subset = @import("../../util.zig").subset; pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { switch (T) { u32 => switch (address) { 0x0400_0000 => bus.ppu.engines[0].dispcnt.raw = value, 0x0400_0004 => bus.ppu.io.nds9.dispstat.raw = @truncate(value), 0x0400_0008 => { bus.ppu.engines[0].bg[0].cnt.raw = @truncate(value >> 0); // 0x0400_0008 bus.ppu.engines[0].bg[1].cnt.raw = @truncate(value >> 16); // 0x0400_000A }, 0x0400_000C => { bus.ppu.engines[0].bg[2].cnt.raw = @truncate(value >> 0); // 0x0400_000A bus.ppu.engines[0].bg[3].cnt.raw = @truncate(value >> 16); // 0x0400_000C }, 0x00400_0010 => { bus.ppu.engines[0].bg[0].hofs.raw = @truncate(value >> 0); // 0x0400_0010 bus.ppu.engines[0].bg[0].vofs.raw = @truncate(value >> 16); // 0x0400_0012 }, 0x00400_0014 => { bus.ppu.engines[0].bg[1].hofs.raw = @truncate(value >> 0); // 0x0400_0014 bus.ppu.engines[0].bg[1].vofs.raw = @truncate(value >> 16); // 0x0400_0016 }, 0x00400_0018 => { bus.ppu.engines[0].bg[2].hofs.raw = @truncate(value >> 0); // 0x0400_0018 bus.ppu.engines[0].bg[2].vofs.raw = @truncate(value >> 16); // 0x0400_001A }, 0x00400_001C => { bus.ppu.engines[0].bg[3].hofs.raw = @truncate(value >> 0); // 0x0400_001C bus.ppu.engines[0].bg[3].vofs.raw = @truncate(value >> 16); // 0x0400_001E }, // DMA Transfers 0x0400_00B0...0x0400_00DC => dma.write(T, &bus.dma, address, value), 0x0400_00E0...0x0400_00EC => std.mem.writeInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], value, .little), // Timers 0x0400_0100...0x0400_010C => log.warn("TODO(timer): write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), 0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds9, value), 0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds9, value), 0x0400_0188 => bus.io.shr.ipc.send(.nds9, value), 0x0400_0208 => bus.io.ime = value & 1 == 1, 0x0400_0210 => bus.io.ie.raw = value, 0x0400_0214 => bus.io.irq.raw &= ~value, 0x0400_0240 => { bus.ppu.vram.io.cnt_a.raw = @truncate(value >> 0); // 0x0400_0240 bus.ppu.vram.io.cnt_b.raw = @truncate(value >> 8); // 0x0400_0241 bus.ppu.vram.io.cnt_c.raw = @truncate(value >> 16); // 0x0400_0242 bus.ppu.vram.io.cnt_d.raw = @truncate(value >> 24); // 0x0400_0243 bus.ppu.vram.update(); }, 0x0400_0244 => { bus.ppu.vram.io.cnt_e.raw = @truncate(value >> 0); // 0x0400_0244 bus.ppu.vram.io.cnt_f.raw = @truncate(value >> 8); // 0x0400_0245 bus.ppu.vram.io.cnt_g.raw = @truncate(value >> 16); // 0x0400_0246 bus.io.shr.wramcnt.raw = @truncate(value >> 24); // 0x0400_0247 bus.ppu.vram.update(); bus.wram.update(bus.io.shr.wramcnt); }, 0x0400_0280 => { bus.io.div.cnt.raw = value; bus.io.div.schedule(bus.scheduler); }, 0x0400_0290, 0x0400_0294 => { bus.io.div.numerator = subset(u64, u32, address, bus.io.div.numerator, value); bus.io.div.schedule(bus.scheduler); }, 0x0400_0298, 0x0400_029C => { bus.io.div.denominator = subset(u64, u32, address, bus.io.div.denominator, value); bus.io.div.schedule(bus.scheduler); }, 0x0400_02B0 => { bus.io.sqrt.cnt.raw = value; bus.io.sqrt.schedule(bus.scheduler); }, 0x0400_02B8, 0x0400_02BC => { bus.io.sqrt.param = subset(u64, u32, address, bus.io.sqrt.param, value); bus.io.sqrt.schedule(bus.scheduler); }, // Engine B 0x0400_0304 => bus.ppu.io.powcnt.raw = value, 0x0400_1000 => bus.ppu.engines[1].dispcnt.raw = value, 0x0400_1008 => { bus.ppu.engines[1].bg[0].cnt.raw = @truncate(value >> 0); // 0x0400_1008 bus.ppu.engines[1].bg[1].cnt.raw = @truncate(value >> 16); // 0x0400_100A }, 0x0400_100C => { bus.ppu.engines[1].bg[2].cnt.raw = @truncate(value >> 0); // 0x0400_100A bus.ppu.engines[1].bg[3].cnt.raw = @truncate(value >> 16); // 0x0400_100C }, 0x00400_1010 => { bus.ppu.engines[1].bg[0].hofs.raw = @truncate(value >> 0); // 0x0400_1010 bus.ppu.engines[1].bg[0].vofs.raw = @truncate(value >> 16); // 0x0400_1012 }, 0x00400_1014 => { bus.ppu.engines[1].bg[1].hofs.raw = @truncate(value >> 0); // 0x0400_1014 bus.ppu.engines[1].bg[1].vofs.raw = @truncate(value >> 16); // 0x0400_1016 }, 0x00400_1018 => { bus.ppu.engines[1].bg[2].hofs.raw = @truncate(value >> 0); // 0x0400_1018 bus.ppu.engines[1].bg[2].vofs.raw = @truncate(value >> 16); // 0x0400_101A }, 0x00400_101C => { bus.ppu.engines[1].bg[3].hofs.raw = @truncate(value >> 0); // 0x0400_101C bus.ppu.engines[1].bg[3].vofs.raw = @truncate(value >> 16); // 0x0400_101E }, else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), }, u16 => switch (address) { 0x0400_0004 => bus.ppu.io.nds9.dispstat.raw = value, // DMA Transfers 0x0400_00B0...0x0400_00DE => dma.write(T, &bus.dma, address, value), 0x0400_00E0...0x0400_00EE => std.mem.writeInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], value, .little), // Timers 0x0400_0100...0x0400_010E => log.warn("TODO(timer): write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), 0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds9, value), 0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds9, value), 0x0400_0208 => bus.io.ime = value & 1 == 1, 0x0400_0280 => { bus.io.div.cnt.raw = value; bus.io.div.schedule(bus.scheduler); }, 0x0400_02B0 => { bus.io.sqrt.cnt.raw = value; bus.io.sqrt.schedule(bus.scheduler); }, 0x0400_0304 => bus.ppu.io.powcnt.raw = value, 0x0400_1008 => bus.ppu.engines[1].bg[0].cnt.raw = value, 0x0400_100A => bus.ppu.engines[1].bg[1].cnt.raw = value, 0x0400_100C => bus.ppu.engines[1].bg[2].cnt.raw = value, 0x0400_100E => bus.ppu.engines[1].bg[3].cnt.raw = value, 0x0400_1010 => bus.ppu.engines[1].bg[0].hofs.raw = value, 0x0400_1012 => bus.ppu.engines[1].bg[0].vofs.raw = value, 0x0400_1014 => bus.ppu.engines[1].bg[1].hofs.raw = value, 0x0400_1016 => bus.ppu.engines[1].bg[1].vofs.raw = value, 0x0400_1018 => bus.ppu.engines[1].bg[2].hofs.raw = value, 0x0400_101A => bus.ppu.engines[1].bg[2].vofs.raw = value, 0x0400_101C => bus.ppu.engines[1].bg[3].hofs.raw = value, 0x0400_101E => bus.ppu.engines[1].bg[3].vofs.raw = value, else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>4})", .{ T, address, value }), }, u8 => switch (address) { // DMA Transfers 0x0400_00B0...0x0400_00DF => dma.write(T, &bus.dma, address, value), 0x0400_00E0...0x0400_00EF => std.mem.writeInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], value, .little), // Timers 0x0400_0100...0x0400_010F => log.warn("TODO(timer): write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), 0x0400_0208 => bus.io.ime = value & 1 == 1, 0x0400_0240 => { bus.ppu.vram.io.cnt_a.raw = value; bus.ppu.vram.update(); }, 0x0400_0241 => { bus.ppu.vram.io.cnt_b.raw = value; bus.ppu.vram.update(); }, 0x0400_0242 => { bus.ppu.vram.io.cnt_c.raw = value; bus.ppu.vram.update(); }, 0x0400_0243 => { bus.ppu.vram.io.cnt_d.raw = value; bus.ppu.vram.update(); }, 0x0400_0244 => { bus.ppu.vram.io.cnt_e.raw = value; bus.ppu.vram.update(); }, 0x0400_0245 => { bus.ppu.vram.io.cnt_f.raw = value; bus.ppu.vram.update(); }, 0x0400_0246 => { bus.ppu.vram.io.cnt_g.raw = value; bus.ppu.vram.update(); }, 0x0400_0247 => { bus.io.shr.wramcnt.raw = value; bus.wram.update(bus.io.shr.wramcnt); }, 0x0400_0248 => { bus.ppu.vram.io.cnt_h.raw = value; bus.ppu.vram.update(); }, 0x0400_0249 => { bus.ppu.vram.io.cnt_i.raw = value; bus.ppu.vram.update(); }, else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>2})", .{ T, address, value }), }, else => @compileError(T ++ " is an unsupported bus write type"), } } fn warn(comptime format: []const u8, args: anytype) u0 { log.warn(format, args); return 0; } pub const PowCnt = extern union { // Enable flag for both LCDs lcd: Bit(u32, 0), engine2d_a: Bit(u32, 1), render3d: Bit(u32, 2), geometry3d: Bit(u32, 3), engine2d_b: Bit(u32, 9), display_swap: Bit(u32, 15), raw: u32, }; /// Divisor const Divisor = struct { const Scheduler = @import("../Scheduler.zig"); /// DIVCNT - Division Control (R/W) cnt: Cnt = .{ .raw = 0x0000_0000 }, /// DIV_NUMER - division numerator (R/W) numerator: u64 = 0x0000_0000_0000_0000, /// DIV_DENOM - division denominator (R/W) denominator: u64 = 0x0000_0000_0000_0000, /// DIV_RESULT - division quotient (R) result: u64 = 0x0000_0000_0000_0000, /// DIVREM_RESULT - remainder remainder: u64 = 0x0000_0000_0000_0000, const Cnt = extern union { mode: Bitfield(u32, 0, 2), div_by_zero: Bit(u32, 14), busy: Bit(u32, 15), raw: u32, }; pub fn schedule(self: *@This(), scheduler: *Scheduler) void { defer self.cnt.busy.set(); const cycle_count: u64 = switch (self.cnt.mode.read()) { 0b00 => 18, 0b01, 0b10, 0b11 => 34, }; scheduler.remove(.{ .nds9 = .div }); scheduler.push(.{ .nds9 = .div }, cycle_count); } pub fn onDivCalc(self: *@This()) void { defer self.cnt.busy.unset(); self.cnt.div_by_zero.write(self.denominator == 0); switch (self.cnt.mode.read()) { 0b00 => { // 32bit / 32bit = 32bit , 32bit const left = sext(i64, i32, self.numerator); const right = sext(i64, i32, self.denominator); if (right == 0) { self.remainder = @bitCast(left); self.result = if (left >> 63 & 1 == 1) @as(u64, 1) else @bitCast(@as(i64, -1)); // FIXME(chore): replace `>> 32 ) << 32` with mask self.result = masks.mask(self.result, (~self.result >> 32) << 32, 0xFFFF_FFFF << 32); return; } self.result = @bitCast(@divTrunc(left, right)); self.remainder = @bitCast(@rem(left, right)); }, 0b01, 0b11 => { // 64bit / 32bit = 64bit , 32bit const left = sext(i128, i64, self.numerator); const right = sext(i128, i32, self.denominator); if (right == 0) { self.remainder = @bitCast(@as(i64, @truncate(left))); self.result = if (left >> 63 & 1 == 1) @as(u64, 1) else @bitCast(@as(i64, -1)); return; } self.result = @bitCast(@as(i64, @truncate(@divTrunc(left, right)))); self.remainder = @bitCast(@as(i64, @truncate(@rem(left, right)))); }, 0b10 => { // 64bit / 64bit = 64bit , 64bit const left = sext(i128, i64, self.numerator); const right = sext(i128, i64, self.denominator); if (right == 0) { self.remainder = @bitCast(@as(i64, @truncate(left))); self.result = if (left >> 63 & 1 == 1) @as(u64, 1) else @bitCast(@as(i64, -1)); return; } self.result = @bitCast(@as(i64, @truncate(@divTrunc(left, right)))); self.remainder = @bitCast(@as(i64, @truncate(@rem(left, right)))); }, } } }; /// Square Root Unit const SquareRootUnit = struct { const Scheduler = @import("../Scheduler.zig"); /// SQRTCNT - Division Control (R/W) cnt: Cnt = .{ .raw = 0x0000_0000 }, /// SQRT_RESULT - square root result (R) result: u32 = 0x0000_0000, /// SQRT_PARAM - square root paramater input (R/W) param: u64 = 0x0000_0000_0000_0000, const Cnt = extern union { mode: Bit(u32, 0), busy: Bit(u32, 15), raw: u32, }; pub fn schedule(self: *@This(), scheduler: *Scheduler) void { defer self.cnt.busy.set(); scheduler.remove(.{ .nds9 = .sqrt }); scheduler.push(.{ .nds9 = .sqrt }, 13); // always takes 13 cycles } pub fn onSqrtCalc(self: *@This()) void { defer self.cnt.busy.unset(); const mask: u64 = blk: { const value: u64 = @intFromBool(!self.cnt.mode.read()); break :blk (value << 32) -% 1; }; self.result = @truncate(std.math.sqrt(self.param & mask)); } }; pub const DispcntA = extern union { bg_mode: Bitfield(u32, 0, 3), /// toggle between 2D and 3D for BG0 bg0_dimension: Bit(u32, 3), tile_obj_mapping: Bit(u32, 4), bitmap_obj_2d_dimension: Bit(u32, 5), bitmap_obj_mapping: Bit(u32, 6), forced_blank: Bit(u32, 7), bg_enable: Bitfield(u32, 8, 4), obj_enable: Bit(u32, 12), win_enable: Bitfield(u32, 13, 2), obj_win_enable: Bit(u32, 15), display_mode: Bitfield(u32, 16, 2), vram_block: Bitfield(u32, 18, 2), tile_obj_1d_boundary: Bitfield(u32, 20, 2), bitmap_obj_1d_boundary: Bit(u32, 22), obj_during_hblank: Bit(u32, 23), char_base: Bitfield(u32, 24, 3), screen_base: Bitfield(u32, 27, 3), bg_ext_pal_enable: Bit(u32, 30), obj_ext_pal_enable: Bit(u32, 31), raw: u32, }; pub const DispcntB = extern union { bg_mode: Bitfield(u32, 0, 3), tile_obj_mapping: Bit(u32, 4), bitmap_obj_2d_dimension: Bit(u32, 5), bitmap_obj_mapping: Bit(u32, 6), forced_blank: Bit(u32, 7), bg_enable: Bitfield(u32, 8, 4), obj_enable: Bit(u32, 12), win_enable: Bitfield(u32, 13, 2), obj_win_enable: Bit(u32, 15), display_mode: Bitfield(u32, 16, 2), tile_obj_1d_boundary: Bitfield(u32, 20, 2), obj_during_hblank: Bit(u32, 23), bg_ext_pal_enable: Bit(u32, 30), obj_ext_pal_enable: Bit(u32, 31), raw: u32, }; pub const Vramcnt = struct { /// Can be used by VRAM-A and VRAM-B pub const A = extern union { mst: Bitfield(u8, 0, 2), offset: Bitfield(u8, 3, 2), enable: Bit(u8, 7), raw: u8, }; /// Can be used by VRAM-C, VRAM-D, VRAM-F, VRAM-G pub const C = extern union { mst: Bitfield(u8, 0, 3), offset: Bitfield(u8, 3, 2), enable: Bit(u8, 7), raw: u8, }; /// Can be used by VRAM-E pub const E = extern union { mst: Bitfield(u8, 0, 3), enable: Bit(u8, 7), raw: u8, }; /// can be used by VRAM-H and VRAM-I pub const H = extern union { mst: Bitfield(u8, 0, 2), enable: Bit(u8, 7), raw: u8, }; }; // Compared to the GBA: // - LY/LYC values are now 9-bits pub const Vcount = extern union { scanline: Bitfield(u16, 0, 9), raw: u16, }; pub const Dispstat = extern union { vblank: Bit(u16, 0), hblank: Bit(u16, 1), coincidence: Bit(u16, 2), vblank_irq: Bit(u16, 3), hblank_irq: Bit(u16, 4), vcount_irq: Bit(u16, 5), /// FIXME: confirm that I'm reading DISPSTAT.7 correctly into LYC lyc: Bitfield(u16, 7, 9), raw: u16, }; pub const Bgcnt = extern union { priority: Bitfield(u16, 0, 2), char_base: Bitfield(u16, 2, 4), mosaic_enable: Bit(u16, 6), colour_mode: Bit(u16, 7), screen_base: Bitfield(u16, 8, 5), display_overflow: Bit(u16, 13), size: Bitfield(u16, 14, 2), raw: u16, }; /// Write Only const BackgroundOffset = extern union { offset: Bitfield(u16, 0, 9), raw: u16, }; pub const Hofs = BackgroundOffset; pub const Vofs = BackgroundOffset; pub const DmaCnt = extern union { dad_adj: Bitfield(u16, 5, 2), sad_adj: Bitfield(u16, 7, 2), repeat: Bit(u16, 9), transfer_type: Bit(u16, 10), start_timing: Bitfield(u16, 11, 3), irq: Bit(u16, 14), enabled: Bit(u16, 15), raw: u16, };