diff --git a/build.zig b/build.zig index 386920b..072cc7d 100644 --- a/build.zig +++ b/build.zig @@ -1,22 +1,91 @@ -const std = @import("std"); const atsam = @import("atsam"); +const MicroZig = @import("microzig"); +const std = @import("std"); -pub const py_badge = .{ +pub const py_badge: MicroZig.Target = .{ .preferred_format = .elf, .chip = atsam.chips.atsamd51j19, .hal = null, }; pub fn build(b: *std.Build) void { - const microzig = @import("microzig").init(b, "microzig"); + const mz = MicroZig.init(b, "microzig"); const optimize = b.standardOptimizeOption(.{}); - const firmware = microzig.addFirmware(b, .{ + const fw_options = b.addOptions(); + fw_options.addOption(bool, "have_cart", false); + + const modified_memory_regions = b.allocator.dupe(MicroZig.MemoryRegion, py_badge.chip.memory_regions) catch @panic("out of memory"); + for (modified_memory_regions) |*memory_region| { + if (memory_region.kind != .ram) continue; + memory_region.offset += 0x19A0; + memory_region.length -= 0x19A0; + break; + } + var modified_py_badge = py_badge; + modified_py_badge.chip.memory_regions = modified_memory_regions; + + const fw = mz.addFirmware(b, .{ .name = "pybadge-io", - .target = py_badge, + .target = modified_py_badge, .optimize = optimize, .source_file = .{ .path = "src/main.zig" }, }); - microzig.installFirmware(b, firmware, .{}); - microzig.installFirmware(b, firmware, .{ .format = .{ .uf2 = .SAMD51 } }); + fw.artifact.step.dependOn(&fw_options.step); + fw.modules.app.dependencies.put("options", fw_options.createModule()) catch @panic("out of memory"); + mz.installFirmware(b, fw, .{}); + mz.installFirmware(b, fw, .{ .format = .{ .uf2 = .SAMD51 } }); +} + +pub const Cart = struct { + mz: *MicroZig, + fw: *MicroZig.Firmware, +}; + +pub const CartOptions = struct { + name: []const u8, + optimize: std.builtin.OptimizeMode, + source_file: std.Build.LazyPath, +}; + +pub fn addCart( + d: *std.Build.Dependency, + b: *std.Build, + options: CartOptions, +) *Cart { + const cart_lib = b.addStaticLibrary(.{ + .name = "cart", + .root_source_file = options.source_file, + .target = py_badge.chip.cpu.getDescriptor().target, + .optimize = options.optimize, + .link_libc = false, + .single_threaded = true, + .use_llvm = true, + .use_lld = true, + }); + cart_lib.addModule("wasm4", d.builder.createModule(.{ .source_file = .{ .path = "src/wasm4.zig" } })); + + const fw_options = b.addOptions(); + fw_options.addOption(bool, "have_cart", true); + + const mz = MicroZig.init(d.builder, "microzig"); + const fw = mz.addFirmware(d.builder, .{ + .name = options.name, + .target = py_badge, + .optimize = .Debug, // TODO + .source_file = .{ .path = "src/main.zig" }, + .linker_script = .{ .source_file = .{ .path = "src/cart.ld" } }, + }); + fw.artifact.linkLibrary(cart_lib); + fw.artifact.step.dependOn(&fw_options.step); + fw.modules.app.dependencies.put("options", fw_options.createModule()) catch @panic("out of memory"); + + const cart: *Cart = b.allocator.create(Cart) catch @panic("out of memory"); + cart.* = .{ .mz = mz, .fw = fw }; + return cart; +} + +pub fn installCart(b: *std.Build, cart: *Cart) void { + cart.mz.installFirmware(b, cart.fw, .{}); + cart.mz.installFirmware(b, cart.fw, .{ .format = .{ .uf2 = .SAMD51 } }); } diff --git a/build.zig.zon b/build.zig.zon index 29b01db..d9a77ba 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -10,8 +10,8 @@ .dependencies = .{ .microzig = .{ - .url = "https://github.com/ZigEmbeddedGroup/microzig/archive/c3baa4a2777634a2b5cd366b87b453e9b72b4b26.tar.gz", - .hash = "122081c44c02c5d6534cad430371a0301169432b04240ca93335e16e16532dd61f06", + .url = "https://github.com/ZigEmbeddedGroup/microzig/archive/57e4379062e396b354d70d5b924d5da2e8905bd4.tar.gz", + .hash = "1220b8b04be6b97020eb19fca51b3cb44c33f5a619c3e36fc48faab8b15261dfff2a", }, .atsam = .{ .url = "https://github.com/ZigEmbeddedGroup/microchip-atsam/archive/93ddffbcbe7a0b849cca610ee933210280d731b4.tar.gz", diff --git a/src/Port.zig b/src/Port.zig index dfa59b2..7208df0 100644 --- a/src/Port.zig +++ b/src/Port.zig @@ -61,23 +61,25 @@ group: Group, pin: u5, pub const Group = enum { A, B }; +pub const Direction = enum { in, out }; +pub const Level = enum { low, high }; -pub inline fn setDir(port: Port, dir: enum { in, out }) void { +pub inline fn setDir(port: Port, dir: Direction) void { switch (dir) { .in => port.groupPtr().DIRCLR.write(.{ .DIRCLR = @as(u32, 1) << port.pin }), .out => port.groupPtr().DIRSET.write(.{ .DIRSET = @as(u32, 1) << port.pin }), } } -pub inline fn write(port: Port, value: bool) void { - switch (value) { - false => port.groupPtr().OUTCLR.write(.{ .OUTCLR = @as(u32, 1) << port.pin }), - true => port.groupPtr().OUTSET.write(.{ .OUTSET = @as(u32, 1) << port.pin }), +pub inline fn write(port: Port, level: Level) void { + switch (level) { + .low => port.groupPtr().OUTCLR.write(.{ .OUTCLR = @as(u32, 1) << port.pin }), + .high => port.groupPtr().OUTSET.write(.{ .OUTSET = @as(u32, 1) << port.pin }), } } -pub inline fn read(port: Port) bool { - return port.groupPtr().IN.read() & @as(u32, 1) << port.pin != 0; +pub inline fn read(port: Port) Level { + return @enumFromInt(port.groupPtr().IN.read().IN >> port.pin & 1); } pub const Mux = enum(u4) { A, B, C, D, E, F, G, H, I, J, K, L, M, N }; diff --git a/src/audio.zig b/src/audio.zig index 94ccbde..bebe218 100644 --- a/src/audio.zig +++ b/src/audio.zig @@ -1,11 +1,33 @@ -var sample_buffer_storage: [2][512]u16 = .{.{0} ** 512} ** 2; pub const sample_buffer: *volatile [2][512]u16 = &sample_buffer_storage; +pub const Channel = struct { + duty: u32, + phase: u32, + phase_step: u31, + phase_step_step: i32, + + duration: u31, + attack_duration: u31, + decay_duration: u31, + sustain_duration: u31, + release_duration: u31, + + volume: u31, + volume_step: i32, + peak_volume: u31, + sustain_volume: u31, + attack_volume_step: i32, + decay_volume_step: i32, + release_volume_step: i32, +}; + pub fn init() void { + @setCold(true); + Port.A0.setDir(.out); Port.AVCC.setDir(.in); Port.SPKR_EN.setDir(.out); - Port.SPKR_EN.write(false); + Port.SPKR_EN.write(.low); io.MCLK.APBDMASK.modify(.{ .DAC_ = 1 }); io.GCLK.PCHCTRL[GCLK.PCH.DAC].write(.{ @@ -131,7 +153,6 @@ pub fn init() void { .padding = 0, }); io.TC5.COUNT8.PER.write(.{ .PER = 12 - 1 }); - while (io.TC5.COUNT8.SYNCBUSY.read().CC0 != 0) {} io.TC5.COUNT8.CTRLA.write(.{ .SWRST = 0, .ENABLE = 1, @@ -189,6 +210,7 @@ pub fn init() void { dma.initAudio(); while (io.DAC.STATUS.read().READY0 != 1) {} + Port.SPKR_EN.write(.high); io.NVIC.ISER[32 / 32].write(.{ .SETENA = 1 << 32 % 32 }); } @@ -197,41 +219,83 @@ pub fn mix() void { for (&sample_buffer[ (dma.getAudioPart() + sample_buffer.len - 1) % sample_buffer.len ]) |*out_sample| { - var sample: i16 = 0; + var sample: i32 = 0; inline for (&local_channels) |*channel| { - if (channel.duration > 0) { + if (channel.duty > 0) { // generate sample; - const mask = channel.phase >> 31; - sample += @intCast(((channel.volume + mask) ^ mask) >> 16); + if (channel.phase < channel.duty) { + sample += channel.volume; + } else { + sample -= channel.volume; + } // update channel.phase +%= channel.phase_step; - channel.volume += channel.volume_step; - channel.duration -= 1; + channel.phase_step = @intCast(channel.phase_step + channel.phase_step_step); + channel.volume = @intCast(channel.volume + channel.volume_step); + if (channel.duration > 0) { + channel.duration -= 1; + } else if (channel.attack_duration > 0) { + channel.duration = channel.attack_duration; + channel.attack_duration = 0; + channel.volume = 0; + channel.volume_step = channel.attack_volume_step; + } else if (channel.decay_duration > 0) { + channel.duration = channel.decay_duration; + channel.decay_duration = 0; + channel.volume = channel.peak_volume; + channel.volume_step = channel.decay_volume_step; + } else if (channel.sustain_duration > 0) { + channel.duration = channel.sustain_duration; + channel.sustain_duration = 0; + channel.volume = channel.sustain_volume; + channel.volume_step = 0; + } else if (channel.release_duration > 0) { + channel.duration = channel.release_duration; + channel.release_duration = 0; + channel.volume = channel.sustain_volume; + channel.volume_step = channel.release_volume_step; + } else { + channel.duty = 0; + } } } - out_sample.* = @bitCast(sample -% std.math.minInt(i16)); + out_sample.* = @intCast((sample >> 16) - std.math.minInt(i16)); } channels.* = local_channels; dma.ackAudio(); } -pub fn tone(channel: usize, note: Note) void { - Port.SPKR_EN.write(true); +pub fn setChannel(channel: usize, state: Channel) void { + io.NVIC.ICER[32 / 32].write(.{ .CLRENA = 1 << 32 % 32 }); + channels[channel] = state; + io.NVIC.ISER[32 / 32].write(.{ .SETENA = 1 << 32 % 32 }); +} +pub fn playNote(channel: usize, note: Note) void { const sample_rate: f32 = 44100.0; - const state: Channel = .{ + setChannel(channel, .{ + .duty = 1 << 31, .phase = 0, .phase_step = @intFromFloat(0x1p32 / sample_rate * note.frequency + 0.5), + .phase_step_step = 0, + + .duration = @intFromFloat(note.duration * sample_rate + 0.5), + .attack_duration = 0, + .decay_duration = 0, + .sustain_duration = 0, + .release_duration = 0, + .volume = 0x8000000, .volume_step = 0, - .duration = @intFromFloat(note.duration * sample_rate + 0.5), - }; - io.NVIC.ICER[32 / 32].write(.{ .CLRENA = 1 << 32 % 32 }); - channels[channel] = state; - io.NVIC.ISER[32 / 32].write(.{ .SETENA = 1 << 32 % 32 }); + .peak_volume = 0, + .sustain_volume = 0, + .attack_volume_step = 0, + .decay_volume_step = 0, + .release_volume_step = 0, + }); } -pub fn play(channels_notes: []const []const Note) void { +pub fn playSong(channels_notes: []const []const Note) void { var time: f32 = 0.0; var channels_note_index = [1]usize{0} ** channels.len; var channels_note_start = [1]f32{0.0} ** channels.len; @@ -253,7 +317,7 @@ pub fn play(channels_notes: []const []const Note) void { if (note_index.* > notes.len) continue; const note = notes[note_index.* - 1]; note_start.* = next_note_time; - tone(channel_index, .{ + playNote(channel_index, .{ .duration = @max(note.duration - 0.04, 0.0), .frequency = note.frequency, }); @@ -267,7 +331,7 @@ pub fn play(channels_notes: []const []const Note) void { } pub fn mute() void { - Port.SPKR_EN.write(false); + Port.SPKR_EN.write(.low); } pub const Note = struct { @@ -314,20 +378,28 @@ pub const Note = struct { }; var channels_storage: [4]Channel = .{.{ + .duty = 0, .phase = 0, .phase_step = 0, + .phase_step_step = 0, + + .duration = 0, + .attack_duration = 0, + .decay_duration = 0, + .sustain_duration = 0, + .release_duration = 0, + .volume = 0, .volume_step = 0, - .duration = 0, + .peak_volume = 0, + .sustain_volume = 0, + .attack_volume_step = 0, + .decay_volume_step = 0, + .release_volume_step = 0, }} ** 4; const channels: *volatile [4]Channel = &channels_storage; -const Channel = struct { - phase: i32, - phase_step: i32, - volume: u31, - volume_step: u31, - duration: u32, -}; + +var sample_buffer_storage: [2][512]u16 = .{.{0} ** 512} ** 2; const dma = @import("dma.zig"); const EVSYS = @import("chip.zig").EVSYS; diff --git a/src/cart.ld b/src/cart.ld new file mode 100644 index 0000000..08c0610 --- /dev/null +++ b/src/cart.ld @@ -0,0 +1,86 @@ +/* + * This file was auto-generated by microzig, and then modified with black magic + * + * Target CPU: ARM Cortex-M4 + * Target Chip: ATSAMD51J19A + */ + +ENTRY(microzig_main); + +MEMORY +{ + bootloader (rx!w) : ORIGIN = 0x00000000, LENGTH = 0x00004000 + runtime_flash (rx!w) : ORIGIN = 0x00004000, LENGTH = 0x0002C000 + cart_flash (rx!w) : ORIGIN = 0x00030000, LENGTH = 0x00080000 + cart_fixed (rw!x) : ORIGIN = 0x20000000, LENGTH = 0x000019A0 + cart_ram (rw!x) : ORIGIN = 0x200019A0, LENGTH = 0x0000E660 + runtime_ram (rw!x) : ORIGIN = 0x20010000, LENGTH = 0x00018000 + cart_stack (rw!x) : ORIGIN = 0x20028000, LENGTH = 0x00004000 + runtime_stack (rw!x) : ORIGIN = 0x2002C000, LENGTH = 0x00004000 + backup_ram (rw!x) : ORIGIN = 0x47000000, LENGTH = 0x00002000 + nvm (rx!w) : ORIGIN = 0x00804000, LENGTH = 0x00000200 +} + +SECTIONS +{ + .text.cart : { + *libcart.a:*(.text*) + *libc.a:*(.text*) + *libcompiler_rt.a:*(.text*) + } >cart_flash + + .rodata.cart : { + *libcart.a:*(.rodata*) + *libc.a:*(.rodata*) + *libcompiler_rt.a:*(.rodata*) + } >cart_flash + + .ARM.exidx.cart : { + *libcart.a:*(.ARM.exidx* .gnu.linkonce.armexidx.*) + *libc.a:*(.ARM.exidx* .gnu.linkonce.armexidx.*) + *libcompiler_rt.a:*(.ARM.exidx* .gnu.linkonce.armexidx.*) + } >cart_flash + + .data.cart : { + cart_data_load_start = LOADADDR(.data.cart); + cart_data_start = .; + *libcart.a:*(.data*) + *libc.a:*(.data*) + *libcompiler_rt.a:*(.data*) + cart_data_end = .; + } >cart_ram AT>cart_flash + + .bss.cart (NOLOAD) : { + cart_bss_start = .; + *libcart.a:*(.bss*) + *libc.a:*(.bss*) + *libcompiler_rt.a:*(.bss*) + cart_bss_end = .; + } >cart_ram + + .text : { + KEEP(*(microzig_flash_start)) + *(.text*) + } >runtime_flash + + .rodata : { + *(.rodata*) + } >runtime_flash + + .ARM.exidx : { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } >runtime_flash + + .data : { + microzig_data_load_start = LOADADDR(.data); + microzig_data_start = .; + *(.data*) + microzig_data_end = .; + } >runtime_ram AT>runtime_flash + + .bss (NOLOAD) : { + microzig_bss_start = .; + *(.bss*) + microzig_bss_end = .; + } >runtime_ram +} diff --git a/src/cart.zig b/src/cart.zig new file mode 100644 index 0000000..57adc51 --- /dev/null +++ b/src/cart.zig @@ -0,0 +1,2609 @@ +pub fn init() void { + @setCold(true); + + Port.BUTTON_OUT.setDir(.in); + Port.BUTTON_OUT.configPtr().write(.{ + .PMUXEN = 0, + .INEN = 1, + .PULLEN = 0, + .reserved6 = 0, + .DRVSTR = 0, + .padding = 0, + }); + Port.BUTTON_CLK.setDir(.out); + Port.BUTTON_CLK.write(.high); + Port.BUTTON_LATCH.setDir(.out); + Port.BUTTON_LATCH.write(.high); + + timer.initFrameSync(); + + @memset(@as(*[0x19A0]u8, @ptrFromInt(0x20000000)), 0); + + if (!options.have_cart) return; + + // fill .bss with zeroes + { + const bss_start: [*]u8 = @ptrCast(&libcart.cart_bss_start); + const bss_end: [*]u8 = @ptrCast(&libcart.cart_bss_end); + const bss_len = @intFromPtr(bss_end) - @intFromPtr(bss_start); + + @memset(bss_start[0..bss_len], 0); + } + + // load .data from flash + { + const data_start: [*]u8 = @ptrCast(&libcart.cart_data_start); + const data_end: [*]u8 = @ptrCast(&libcart.cart_data_end); + const data_len = @intFromPtr(data_end) - @intFromPtr(data_start); + const data_src: [*]const u8 = @ptrCast(&libcart.cart_data_load_start); + + @memcpy(data_start[0..data_len], data_src[0..data_len]); + } +} + +pub fn start() void { + call(if (options.have_cart) &libcart.start else &struct { + fn start() callconv(.C) void { + const w4 = @import("wasm4.zig"); + w4.trace("start"); + } + }.start); +} + +pub fn tick() void { + if (!timer.checkFrameReady()) return; + + { + var gamepad: u8 = 0; + timer.delay(1); + Port.BUTTON_LATCH.write(.low); + timer.delay(1); + Port.BUTTON_LATCH.write(.high); + for ([8]u8{ + BUTTON_2, + BUTTON_1, + 1 << 2, + 1 << 3, + BUTTON_RIGHT, + BUTTON_DOWN, + BUTTON_UP, + BUTTON_LEFT, + }) |button| { + timer.delay(1); + switch (Port.BUTTON_OUT.read()) { + .low => {}, + .high => gamepad |= button, + } + timer.delay(1); + Port.BUTTON_CLK.write(.high); + timer.delay(2); + Port.BUTTON_CLK.write(.low); + } + timer.delay(1); + GAMEPAD1.* = gamepad; + } + if (SYSTEM_FLAGS.* & SYSTEM_PRESERVE_FRAMEBUFFER == 0) @memset(FRAMEBUFFER, 0b00_00_00_00); + call(if (options.have_cart) &libcart.update else &struct { + fn update() callconv(.C) void { + const w4 = @import("wasm4.zig"); + const global = struct { + var tick: u8 = 0; + var stroke: bool = true; + var radius: u32 = 0; + var note: usize = 0; + }; + w4.PALETTE[0] = 0x000000; + w4.PALETTE[1] = 0xFF0000; + w4.PALETTE[2] = 0xFFFFFF; + w4.DRAW_COLORS.* = if (global.stroke) 0x0032 else 0x0002; + w4.oval( + @as(i32, lcd.width / 2) -| @min(global.radius, std.math.maxInt(i32)), + @as(i32, lcd.height / 2) -| @min(global.radius, std.math.maxInt(i32)), + global.radius * 2, + global.radius * 2, + ); + w4.DRAW_COLORS.* = 0x0003; + for (0..8) |button| { + if (w4.GAMEPAD1.* & @as(u8, 1) << @intCast(button) != 0) { + w4.text( + &.{0x80 + @as(u8, @intCast(button))}, + 20 + @as(u8, @intCast(button)) * 16, + 60, + ); + } + } + global.tick += 1; + if (global.tick == 10) { + global.tick = 0; + global.stroke = !global.stroke; + if (global.stroke) { + global.radius += 1; + if (global.radius == 100) { + global.radius = 0; + } + } + + w4.tone(([_]u16{ + 880, 831, 784, 740, 698, 659, 622, 587, 554, 523, 494, 466, + 440, 415, 392, 370, 349, 330, 311, 294, 277, 262, 247, 233, + 220, 207, 196, 185, 175, 165, 156, 147, 139, 131, 123, 117, + 110, + })[global.note], 10, 50, w4.TONE_PULSE1); + global.note += 1; + if (global.note == 37) global.note = 0; + } + } + }.update); + var x: u8 = 0; + var y: u8 = 0; + for (FRAMEBUFFER[0 .. lcd.width * lcd.height * 2 / 8]) |byte| { + inline for (.{ 0, 2, 4, 6 }) |shift| { + const palette_index: u2 = @truncate(byte >> shift); + const color: u24 = @truncate(PALETTE[palette_index]); + lcd.fb.bpp24[x][y] = @bitCast(color); + x += 1; + } + if (x == lcd.width) { + x = 0; + y += 1; + } + } + std.debug.assert(y == lcd.height); +} + +pub fn blit(sprite: [*]const User(u8), x: i32, y: i32, rest: *const extern struct { width: User(u32), height: User(u32), flags: User(u32) }) callconv(.C) void { + const width = rest.width.load(); + const height = rest.height.load(); + const flags = rest.flags.load(); + + switch (flags) { + BLIT_1BPP => for (0..height) |sprite_y| { + for (0..width) |sprite_x| { + const sprite_index = sprite_x + width * sprite_y; + const draw_color_index: u1 = @truncate(sprite[sprite_index >> 3].load() >> + (7 - @as(u3, @truncate(sprite_index)))); + clipDraw( + x +| @min(sprite_x, std.math.maxInt(i32)), + y +| @min(sprite_y, std.math.maxInt(i32)), + draw_color_index, + ); + } + }, + BLIT_2BPP => for (0..height) |sprite_y| { + for (0..width) |sprite_x| { + const sprite_index = sprite_x + width * sprite_y; + const draw_color_index: u2 = @truncate(sprite[sprite_index >> 2].load() >> + (6 - (@as(u3, @as(u2, @truncate(sprite_index))) << 1))); + clipDraw( + x +| @min(sprite_x, std.math.maxInt(i32)), + y +| @min(sprite_y, std.math.maxInt(i32)), + draw_color_index, + ); + } + }, + else => {}, + } +} + +pub fn oval(x: i32, y: i32, width: u32, height: u32) callconv(.C) void { + if (width == 0 or height == 0 or x >= SCREEN_SIZE or y >= SCREEN_SIZE) return; + const end_x = x +| @min(width, std.math.maxInt(i32)); + const end_y = y +| @min(height, std.math.maxInt(i32)); + if (end_x < 0 or end_y < 0) return; + + const draw_colors = DRAW_COLORS.*; + const fill_draw_color: u4 = @truncate(draw_colors >> 0); + const stroke_draw_color: u4 = @truncate(draw_colors >> 4); + if (fill_draw_color == 0 and stroke_draw_color == 0) return; + const fill_palette_index: ?u2 = if (fill_draw_color == 0) null else @truncate(fill_draw_color - 1); + const stroke_palette_index: ?u2 = if (stroke_draw_color == 0) fill_palette_index else @truncate(stroke_draw_color - 1); + + switch (std.math.order(width, height)) { + .lt => rect(x, y, width, height), + .eq => { + const size: u31 = @intCast(width >> 1); + const mid_x = x +| size; + const mid_y = y +| size; + + var cur_x: u31 = 0; + var cur_y: u31 = size; + var err: i32 = size >> 1; + while (cur_x <= cur_y) { + hline(mid_x -| cur_y, mid_y -| cur_x, cur_y << 1); + hline(mid_x -| cur_y, mid_y +| cur_x, cur_y << 1); + if (stroke_palette_index) |palette_index| { + clipDrawPalette(mid_x -| cur_x, mid_y -| cur_y, palette_index); + clipDrawPalette(mid_x +| cur_x, mid_y -| cur_y, palette_index); + clipDrawPalette(mid_x -| cur_y, mid_y -| cur_x, palette_index); + clipDrawPalette(mid_x +| cur_y, mid_y -| cur_x, palette_index); + clipDrawPalette(mid_x -| cur_y, mid_y +| cur_x, palette_index); + clipDrawPalette(mid_x +| cur_y, mid_y +| cur_x, palette_index); + clipDrawPalette(mid_x -| cur_x, mid_y +| cur_y, palette_index); + clipDrawPalette(mid_x +| cur_x, mid_y +| cur_y, palette_index); + } + cur_x += 1; + err += cur_x; + var temp = err - cur_y; + if (temp >= 0) { + err = temp; + cur_y -= 1; + + if (cur_x <= cur_y) { + hline(mid_x -| cur_x, mid_y -| cur_y, cur_x << 1); + hline(mid_x -| cur_x, mid_y +| cur_y, cur_x << 1); + } + } + } + }, + .gt => rect(x, y, width, height), + } +} + +pub fn rect(x: i32, y: i32, width: u32, height: u32) callconv(.C) void { + if (width == 0 or height == 0 or x >= SCREEN_SIZE or y >= SCREEN_SIZE) return; + const end_x = x +| @min(width, std.math.maxInt(i32)); + const end_y = y +| @min(height, std.math.maxInt(i32)); + if (end_x < 0 or end_y < 0) return; + + const draw_colors = DRAW_COLORS.*; + const fill_draw_color: u4 = @truncate(draw_colors >> 0); + const stroke_draw_color: u4 = @truncate(draw_colors >> 4); + if (fill_draw_color == 0 and stroke_draw_color == 0) return; + const fill_palette_index: ?u2 = if (fill_draw_color == 0) null else @truncate(fill_draw_color - 1); + const stroke_palette_index: ?u2 = if (stroke_draw_color == 0) fill_palette_index else @truncate(stroke_draw_color - 1); + + if (stroke_palette_index) |palette_index| { + if (y >= 0 and y < SCREEN_SIZE) { + for (@max(x, 0)..@intCast(@min(end_x, SCREEN_SIZE))) |cur_x| { + drawPalette(@intCast(cur_x), @intCast(y), palette_index); + } + } + } + if (height > 2) { + for (@max(y + 1, 0)..@intCast(@min(end_y - 1, SCREEN_SIZE))) |cur_y| { + if (stroke_palette_index) |palette_index| { + if (x >= 0 and x < SCREEN_SIZE) { + drawPalette(@intCast(x), @intCast(cur_y), palette_index); + } + } + if (fill_palette_index) |palette_index| { + if (width > 2) { + for (@max(x + 1, 0)..@intCast(@min(end_x - 1, SCREEN_SIZE))) |cur_x| { + drawPalette(@intCast(cur_x), @intCast(cur_y), palette_index); + } + } + } + if (stroke_palette_index) |palette_index| { + if (width > 1 and end_x - 1 >= 0 and end_x - 1 < SCREEN_SIZE) { + drawPalette(@intCast(end_x - 1), @intCast(cur_y), palette_index); + } + } + } + } + if (stroke_palette_index) |palette_index| { + if (height > 1 and end_y - 1 >= 0 and end_y - 1 < SCREEN_SIZE) { + for (@max(x, 0)..@intCast(@min(end_x, SCREEN_SIZE))) |cur_x| { + drawPalette(@intCast(cur_x), @intCast(end_y - 1), palette_index); + } + } + } +} + +pub fn text(str: [*]const User(u8), len: usize, x: i32, y: i32) callconv(.C) void { + var cur_x = x; + var cur_y = y; + for (str[0..len]) |*byte| switch (byte.load()) { + else => cur_x +|= 8, + '\n' => { + cur_x = x; + cur_y +|= 8; + }, + ' '...0xFF => |char| { + const glyph = &font[char - ' ']; + blitUnsafe(glyph, cur_x, cur_y, 8, 8, BLIT_1BPP); + cur_x +|= 8; + }, + }; +} + +pub fn vline(x: i32, y: i32, len: u32) callconv(.C) void { + if (len == 0 or x < 0 or x >= SCREEN_SIZE or y >= SCREEN_SIZE) return; + const end_y = y +| @min(len, std.math.maxInt(i32)); + if (end_y < 0) return; + + const draw_color: u4 = @truncate(DRAW_COLORS.* >> 0); + if (draw_color == 0) return; + const palette_index: u2 = @truncate(draw_color - 1); + + for (@max(y, 0)..@intCast(@min(end_y, SCREEN_SIZE))) |cur_y| { + drawPalette(@intCast(x), @intCast(cur_y), palette_index); + } +} + +pub fn hline(x: i32, y: i32, len: u32) callconv(.C) void { + if (len == 0 or y < 0 or y >= SCREEN_SIZE or x >= SCREEN_SIZE) return; + const end_x = x +| @min(len, std.math.maxInt(i32)); + if (end_x < 0) return; + + const draw_color: u4 = @truncate(DRAW_COLORS.* >> 0); + if (draw_color == 0) return; + const palette_index: u2 = @truncate(draw_color - 1); + + for (@max(x, 0)..@intCast(@min(end_x, SCREEN_SIZE))) |cur_x| { + drawPalette(@intCast(cur_x), @intCast(y), palette_index); + } +} + +pub fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) callconv(.C) void { + const start_frequency: u16 = @truncate(frequency >> 0); + const end_frequency = switch (@as(u16, @truncate(frequency >> 16))) { + 0 => start_frequency, + else => |end_frequency| end_frequency, + }; + const sustain_time: u8 = @truncate(duration >> 0); + const release_time: u8 = @truncate(duration >> 8); + const decay_time: u8 = @truncate(duration >> 16); + const attack_time: u8 = @truncate(duration >> 24); + const total_time = @as(u10, attack_time) + decay_time + sustain_time + release_time; + const sustain_volume: u8 = @truncate(volume >> 0); + const peak_volume = switch (@as(u8, @truncate(volume >> 8))) { + 0 => 100, + else => |attack_volume| attack_volume, + }; + const channel: enum { pulse1, pulse2, triangle, noise } = @enumFromInt(@as(u2, @truncate(flags >> 0))); + + var state: audio.Channel = .{ + .duty = 0, + .phase = 0, + .phase_step = 0, + .phase_step_step = 0, + + .duration = 0, + .attack_duration = 0, + .decay_duration = 0, + .sustain_duration = 0, + .release_duration = 0, + + .volume = 0, + .volume_step = 0, + .peak_volume = 0, + .sustain_volume = 0, + .attack_volume_step = 0, + .decay_volume_step = 0, + .release_volume_step = 0, + }; + + const start_phase_step = @mulWithOverflow((1 << 32) / 44100, @as(u31, start_frequency)); + const end_phase_step = @mulWithOverflow((1 << 32) / 44100, @as(u31, end_frequency)); + if (start_phase_step[1] != 0 or end_phase_step[1] != 0) return; + state.phase_step = start_phase_step[0]; + state.phase_step_step = @divTrunc(@as(i32, end_phase_step[0]) - start_phase_step[0], @as(u20, total_time) * @divExact(44100, 60)); + + state.attack_duration = @as(u18, attack_time) * @divExact(44100, 60); + state.decay_duration = @as(u18, decay_time) * @divExact(44100, 60); + state.sustain_duration = @as(u18, sustain_time) * @divExact(44100, 60); + state.release_duration = @as(u18, release_time) * @divExact(44100, 60); + + state.peak_volume = @as(u29, peak_volume) << 21; + state.sustain_volume = @as(u29, sustain_volume) << 21; + if (state.attack_duration > 0) { + state.attack_volume_step = @divTrunc(@as(i32, state.peak_volume) - 0, state.attack_duration); + } + if (state.decay_duration > 0) { + state.decay_volume_step = @divTrunc(@as(i32, state.sustain_volume) - state.peak_volume, state.decay_duration); + } + if (state.release_duration > 0) { + state.release_volume_step = @divTrunc(@as(i32, 0) - state.sustain_volume, state.release_duration); + } + + switch (channel) { + .pulse1, .pulse2 => { + const mode: enum { @"1/8", @"1/4", @"1/2", @"3/4" } = @enumFromInt(@as(u2, @truncate(flags >> 2))); + state.duty = switch (mode) { + .@"1/8" => (1 << 32) / 8, + .@"1/4" => (1 << 32) / 4, + .@"1/2" => (1 << 32) / 2, + .@"3/4" => (3 << 32) / 4, + }; + }, + .triangle => { + state.duty = (1 << 32) / 2; + }, + .noise => { + state.duty = (1 << 32) / 2; + }, + } + + audio.setChannel(@intFromEnum(channel), state); +} + +pub fn trace(str: [*]const User(u8), len: usize) callconv(.C) void { + std.log.scoped(.trace).info("{}", .{fmtUserString(str[0..len])}); +} + +fn call(func: *const fn () callconv(.C) void) void { + const process_stack = utils.HSRAM.ADDR[utils.HSRAM.SIZE - @divExact( + utils.HSRAM.SIZE, + 3 * 2, + ) ..][0..@divExact(utils.HSRAM.SIZE, 3 * 4)]; + const frame = comptime std.mem.bytesAsSlice(u32, process_stack[process_stack.len - 0x20 ..]); + @memset(frame[0..5], 0); + frame[5] = @intFromPtr(&libcart.__return_thunk__); + frame[6] = @intFromPtr(func); + frame[7] = 1 << 24; + asm volatile ( + \\ msr psp, %[process_stack] + \\ svc #12 + : + : [process_stack] "r" (frame.ptr), + : "memory" + ); +} + +fn blitUnsafe(sprite: [*]const u8, x: i32, y: i32, width: u32, height: u32, flags: u32) void { + switch (flags) { + BLIT_1BPP => for (0..height) |sprite_y| { + for (0..width) |sprite_x| { + const sprite_index = sprite_x + width * sprite_y; + const draw_color_index: u1 = @truncate(sprite[sprite_index >> 3] >> + (7 - @as(u3, @truncate(sprite_index)))); + clipDraw( + x +| @min(sprite_x, std.math.maxInt(i32)), + y +| @min(sprite_y, std.math.maxInt(i32)), + draw_color_index, + ); + } + }, + BLIT_2BPP => for (0..height) |sprite_y| { + for (0..width) |sprite_x| { + const sprite_index = sprite_x + width * sprite_y; + const draw_color_index: u2 = @truncate(sprite[sprite_index >> 2] >> + (6 - (@as(u3, @as(u2, @truncate(sprite_index))) << 1))); + clipDraw( + x +| @min(sprite_x, std.math.maxInt(i32)), + y +| @min(sprite_y, std.math.maxInt(i32)), + draw_color_index, + ); + } + }, + else => {}, + } +} + +inline fn clipDraw(x: i32, y: i32, draw_color_index: u2) void { + if (x < 0 or x >= SCREEN_SIZE or y < 0 or y >= SCREEN_SIZE) return; + draw(@intCast(x), @intCast(y), draw_color_index); +} + +inline fn clipDrawPalette(x: i32, y: i32, palette_index: u2) void { + if (x < 0 or x >= SCREEN_SIZE or y < 0 or y >= SCREEN_SIZE) return; + drawPalette(@intCast(x), @intCast(y), palette_index); +} + +inline fn draw(x: u8, y: u8, draw_color_index: u2) void { + const draw_color: u4 = @truncate(DRAW_COLORS.* >> (@as(u4, draw_color_index) << 2)); + if (draw_color == 0) return; + const palette_index: u2 = @truncate(draw_color - 1); + drawPalette(x, y, palette_index); +} + +inline fn drawPalette(x: u8, y: u8, palette_index: u2) void { + std.debug.assert(x < SCREEN_SIZE and y < SCREEN_SIZE); + const buffer_index = x + SCREEN_SIZE * y; + const shift = @as(u3, @as(u2, @truncate(buffer_index))) << 1; + const byte = &FRAMEBUFFER[buffer_index >> 2]; + byte.* = (byte.* & ~(@as(u8, 0b11) << shift)) | @as(u8, palette_index) << shift; +} + +fn formatUserString(bytes: []const User(u8), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { + for (bytes) |*byte| try writer.writeByte(byte.load()); +} +inline fn fmtUserString(bytes: []const User(u8)) std.fmt.Formatter(formatUserString) { + return .{ .data = bytes }; +} + +fn User(comptime T: type) type { + return extern struct { + const Self = @This(); + const suffix = switch (@sizeOf(T)) { + 1 => "b", + 2 => "h", + 4 => "", + else => @compileError("loadUser doesn't support " ++ @typeName(T)), + }; + + unsafe: T, + + pub inline fn load(user: *const Self) T { + return asm ("ldr" ++ suffix ++ "t %[value], [%[pointer]]" + : [value] "=r" (-> T), + : [pointer] "r" (&user.unsafe), + ); + } + + pub inline fn store(user: *Self, value: T) void { + asm volatile ("str" ++ suffix ++ "t %[value], [%pointer]]" + : + : [value] "r" (value), + [pointer] "r" (&user.unsafe), + ); + } + }; +} + +const libcart = struct { + extern var cart_data_start: u8; + extern var cart_data_end: u8; + extern var cart_bss_start: u8; + extern var cart_bss_end: u8; + extern const cart_data_load_start: u8; + + extern fn start() void; + extern fn update() void; + extern fn __return_thunk__() noreturn; + + comptime { + if (!options.have_cart) _ = @import("wasm4.zig").__return_thunk__; + } +}; + +pub const SCREEN_SIZE: u32 = 160; + +const PALETTE: *[4]u32 = @ptrFromInt(0x20000004); +const DRAW_COLORS: *u16 = @ptrFromInt(0x20000014); +const GAMEPAD1: *u8 = @ptrFromInt(0x20000016); +const GAMEPAD2: *u8 = @ptrFromInt(0x20000017); +const GAMEPAD3: *u8 = @ptrFromInt(0x20000018); +const GAMEPAD4: *u8 = @ptrFromInt(0x20000019); +const MOUSE_X: *i16 = @ptrFromInt(0x2000001a); +const MOUSE_Y: *i16 = @ptrFromInt(0x2000001c); +const MOUSE_BUTTONS: *u8 = @ptrFromInt(0x2000001e); +const SYSTEM_FLAGS: *u8 = @ptrFromInt(0x2000001f); +const NETPLAY: *u8 = @ptrFromInt(0x20000020); +const FRAMEBUFFER: *[6400]u8 = @ptrFromInt(0x200000A0); + +const BUTTON_1: u8 = 1; +const BUTTON_2: u8 = 2; +const BUTTON_LEFT: u8 = 16; +const BUTTON_RIGHT: u8 = 32; +const BUTTON_UP: u8 = 64; +const BUTTON_DOWN: u8 = 128; + +const MOUSE_LEFT: u8 = 1; +const MOUSE_RIGHT: u8 = 2; +const MOUSE_MIDDLE: u8 = 4; + +const SYSTEM_PRESERVE_FRAMEBUFFER: u8 = 1; +const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2; + +pub const BLIT_2BPP: u32 = 1; +pub const BLIT_1BPP: u32 = 0; +pub const BLIT_FLIP_X: u32 = 2; +pub const BLIT_FLIP_Y: u32 = 4; +pub const BLIT_ROTATE: u32 = 8; + +const font: [0x100 - ' '][8]u8 = .{ .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11000111, + 0b11000111, + 0b11000111, + 0b11001111, + 0b11001111, + 0b11111111, + 0b11001111, + 0b11111111, +}, .{ + 0b10010011, + 0b10010011, + 0b10010011, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b10010011, + 0b00000001, + 0b10010011, + 0b10010011, + 0b10010011, + 0b00000001, + 0b10010011, + 0b11111111, +}, .{ + 0b11101111, + 0b10000011, + 0b00101111, + 0b10000011, + 0b11101001, + 0b00000011, + 0b11101111, + 0b11111111, +}, .{ + 0b10011101, + 0b01011011, + 0b00110111, + 0b11101111, + 0b11011001, + 0b10110101, + 0b01110011, + 0b11111111, +}, .{ + 0b10001111, + 0b00100111, + 0b00100111, + 0b10001111, + 0b00100101, + 0b00110011, + 0b10000001, + 0b11111111, +}, .{ + 0b11001111, + 0b11001111, + 0b11001111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11110011, + 0b11100111, + 0b11001111, + 0b11001111, + 0b11001111, + 0b11100111, + 0b11110011, + 0b11111111, +}, .{ + 0b10011111, + 0b11001111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11001111, + 0b10011111, + 0b11111111, +}, .{ + 0b11111111, + 0b10010011, + 0b11000111, + 0b00000001, + 0b11000111, + 0b10010011, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11100111, + 0b11100111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11001111, + 0b11001111, + 0b10011111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b10000001, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11001111, + 0b11001111, + 0b11111111, +}, .{ + 0b11111101, + 0b11111011, + 0b11110111, + 0b11101111, + 0b11011111, + 0b10111111, + 0b01111111, + 0b11111111, +}, .{ + 0b11000111, + 0b10110011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10011011, + 0b11000111, + 0b11111111, +}, .{ + 0b11100111, + 0b11000111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b10000011, + 0b00111001, + 0b11110001, + 0b11000011, + 0b10000111, + 0b00011111, + 0b00000001, + 0b11111111, +}, .{ + 0b10000001, + 0b11110011, + 0b11100111, + 0b11000011, + 0b11111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11100011, + 0b11000011, + 0b10010011, + 0b00110011, + 0b00000001, + 0b11110011, + 0b11110011, + 0b11111111, +}, .{ + 0b00000011, + 0b00111111, + 0b00000011, + 0b11111001, + 0b11111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11000011, + 0b10011111, + 0b00111111, + 0b00000011, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b00000001, + 0b00111001, + 0b11110011, + 0b11100111, + 0b11001111, + 0b11001111, + 0b11001111, + 0b11111111, +}, .{ + 0b10000111, + 0b00111011, + 0b00011011, + 0b10000111, + 0b01100001, + 0b01111001, + 0b10000011, + 0b11111111, +}, .{ + 0b10000011, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111001, + 0b11110011, + 0b10000111, + 0b11111111, +}, .{ + 0b11111111, + 0b11001111, + 0b11001111, + 0b11111111, + 0b11001111, + 0b11001111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11001111, + 0b11001111, + 0b11111111, + 0b11001111, + 0b11001111, + 0b10011111, + 0b11111111, +}, .{ + 0b11110011, + 0b11100111, + 0b11001111, + 0b10011111, + 0b11001111, + 0b11100111, + 0b11110011, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b00000001, + 0b11111111, + 0b00000001, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b10011111, + 0b11001111, + 0b11100111, + 0b11110011, + 0b11100111, + 0b11001111, + 0b10011111, + 0b11111111, +}, .{ + 0b10000011, + 0b00000001, + 0b00111001, + 0b11110011, + 0b11000111, + 0b11111111, + 0b11000111, + 0b11111111, +}, .{ + 0b10000011, + 0b01111101, + 0b01000101, + 0b01010101, + 0b01000001, + 0b01111111, + 0b10000011, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b00111001, + 0b00111001, + 0b00000001, + 0b00111001, + 0b00111001, + 0b11111111, +}, .{ + 0b00000011, + 0b00111001, + 0b00111001, + 0b00000011, + 0b00111001, + 0b00111001, + 0b00000011, + 0b11111111, +}, .{ + 0b11000011, + 0b10011001, + 0b00111111, + 0b00111111, + 0b00111111, + 0b10011001, + 0b11000011, + 0b11111111, +}, .{ + 0b00000111, + 0b00110011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00110011, + 0b00000111, + 0b11111111, +}, .{ + 0b00000001, + 0b00111111, + 0b00111111, + 0b00000011, + 0b00111111, + 0b00111111, + 0b00000001, + 0b11111111, +}, .{ + 0b00000001, + 0b00111111, + 0b00111111, + 0b00000011, + 0b00111111, + 0b00111111, + 0b00111111, + 0b11111111, +}, .{ + 0b11000001, + 0b10011111, + 0b00111111, + 0b00110001, + 0b00111001, + 0b10011001, + 0b11000001, + 0b11111111, +}, .{ + 0b00111001, + 0b00111001, + 0b00111001, + 0b00000001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b11111111, +}, .{ + 0b10000001, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b11111001, + 0b11111001, + 0b11111001, + 0b11111001, + 0b11111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b00111001, + 0b00110011, + 0b00100111, + 0b00001111, + 0b00000111, + 0b00100011, + 0b00110001, + 0b11111111, +}, .{ + 0b10011111, + 0b10011111, + 0b10011111, + 0b10011111, + 0b10011111, + 0b10011111, + 0b10000001, + 0b11111111, +}, .{ + 0b00111001, + 0b00010001, + 0b00000001, + 0b00000001, + 0b00101001, + 0b00111001, + 0b00111001, + 0b11111111, +}, .{ + 0b00111001, + 0b00011001, + 0b00001001, + 0b00000001, + 0b00100001, + 0b00110001, + 0b00111001, + 0b11111111, +}, .{ + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b00000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00000011, + 0b00111111, + 0b00111111, + 0b11111111, +}, .{ + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00100001, + 0b00110011, + 0b10000101, + 0b11111111, +}, .{ + 0b00000011, + 0b00111001, + 0b00111001, + 0b00110001, + 0b00000111, + 0b00100011, + 0b00110001, + 0b11111111, +}, .{ + 0b10000111, + 0b00110011, + 0b00111111, + 0b10000011, + 0b11111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b10000001, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11111111, +}, .{ + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b00111001, + 0b00111001, + 0b00111001, + 0b00010001, + 0b10000011, + 0b11000111, + 0b11101111, + 0b11111111, +}, .{ + 0b00111001, + 0b00111001, + 0b00101001, + 0b00000001, + 0b00000001, + 0b00010001, + 0b00111001, + 0b11111111, +}, .{ + 0b00111001, + 0b00010001, + 0b10000011, + 0b11000111, + 0b10000011, + 0b00010001, + 0b00111001, + 0b11111111, +}, .{ + 0b10011001, + 0b10011001, + 0b10011001, + 0b11000011, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11111111, +}, .{ + 0b00000001, + 0b11110001, + 0b11100011, + 0b11000111, + 0b10001111, + 0b00011111, + 0b00000001, + 0b11111111, +}, .{ + 0b11000011, + 0b11001111, + 0b11001111, + 0b11001111, + 0b11001111, + 0b11001111, + 0b11000011, + 0b11111111, +}, .{ + 0b01111111, + 0b10111111, + 0b11011111, + 0b11101111, + 0b11110111, + 0b11111011, + 0b11111101, + 0b11111111, +}, .{ + 0b10000111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b10000111, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b00000001, +}, .{ + 0b11101111, + 0b11110111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10000011, + 0b11111001, + 0b10000001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b00111111, + 0b00111111, + 0b00000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10000001, + 0b00111111, + 0b00111111, + 0b00111111, + 0b10000001, + 0b11111111, +}, .{ + 0b11111001, + 0b11111001, + 0b10000001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10000011, + 0b00111001, + 0b00000001, + 0b00111111, + 0b10000011, + 0b11111111, +}, .{ + 0b11110001, + 0b11100111, + 0b10000001, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10000001, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111001, + 0b10000011, +}, .{ + 0b00111111, + 0b00111111, + 0b00000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b11111111, +}, .{ + 0b11100111, + 0b11111111, + 0b11000111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b11110011, + 0b11111111, + 0b11100011, + 0b11110011, + 0b11110011, + 0b11110011, + 0b11110011, + 0b10000111, +}, .{ + 0b00111111, + 0b00111111, + 0b00110001, + 0b00000011, + 0b00000111, + 0b00100011, + 0b00110001, + 0b11111111, +}, .{ + 0b11000111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b00000011, + 0b01001001, + 0b01001001, + 0b01001001, + 0b01001001, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b00000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b00000011, + 0b00111001, + 0b00111001, + 0b00000011, + 0b00111111, + 0b00111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10000001, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111001, + 0b11111001, +}, .{ + 0b11111111, + 0b11111111, + 0b10010001, + 0b10001111, + 0b10011111, + 0b10011111, + 0b10011111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10000011, + 0b00111111, + 0b10000011, + 0b11111001, + 0b00000011, + 0b11111111, +}, .{ + 0b11100111, + 0b11100111, + 0b10000001, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10011001, + 0b10011001, + 0b10011001, + 0b11000011, + 0b11100111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b01001001, + 0b01001001, + 0b01001001, + 0b01001001, + 0b10000001, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b00111001, + 0b00000001, + 0b11000111, + 0b00000001, + 0b00111001, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111001, + 0b10000011, +}, .{ + 0b11111111, + 0b11111111, + 0b00000001, + 0b11100011, + 0b11000111, + 0b10001111, + 0b00000001, + 0b11111111, +}, .{ + 0b11110011, + 0b11100111, + 0b11100111, + 0b11001111, + 0b11100111, + 0b11100111, + 0b11110011, + 0b11111111, +}, .{ + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11111111, +}, .{ + 0b10011111, + 0b11001111, + 0b11001111, + 0b11100111, + 0b11001111, + 0b11001111, + 0b10011111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10001111, + 0b01000101, + 0b11100011, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b10010011, + 0b10010011, + 0b11111111, +}, .{ + 0b10000011, + 0b00010001, + 0b00101001, + 0b00111001, + 0b00101001, + 0b00101001, + 0b10000011, + 0b11111111, +}, .{ + 0b10000011, + 0b00110001, + 0b00101001, + 0b00110001, + 0b00101001, + 0b00110001, + 0b10000011, + 0b11111111, +}, .{ + 0b10000011, + 0b00010001, + 0b00010001, + 0b01111101, + 0b00010001, + 0b00010001, + 0b10000011, + 0b11111111, +}, .{ + 0b10000011, + 0b00000001, + 0b00000001, + 0b01111101, + 0b00000001, + 0b00000001, + 0b10000011, + 0b11111111, +}, .{ + 0b10000011, + 0b00010001, + 0b00100001, + 0b01111101, + 0b00100001, + 0b00010001, + 0b10000011, + 0b11111111, +}, .{ + 0b10000011, + 0b00010001, + 0b00001001, + 0b01111101, + 0b00001001, + 0b00010001, + 0b10000011, + 0b11111111, +}, .{ + 0b10000011, + 0b00010001, + 0b00111001, + 0b01010101, + 0b00010001, + 0b00010001, + 0b10000011, + 0b11111111, +}, .{ + 0b10000011, + 0b00010001, + 0b00010001, + 0b01010101, + 0b00111001, + 0b00010001, + 0b10000011, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11100111, + 0b11111111, + 0b11100111, + 0b11100111, + 0b11000111, + 0b11000111, + 0b11000111, + 0b11111111, +}, .{ + 0b11101111, + 0b10000011, + 0b00101001, + 0b00101111, + 0b00101001, + 0b10000011, + 0b11101111, + 0b11111111, +}, .{ + 0b11000011, + 0b10011001, + 0b10011111, + 0b00000011, + 0b10011111, + 0b10011111, + 0b00000001, + 0b11111111, +}, .{ + 0b11111111, + 0b10100101, + 0b11011011, + 0b11011011, + 0b11011011, + 0b10100101, + 0b11111111, + 0b11111111, +}, .{ + 0b10011001, + 0b10011001, + 0b11000011, + 0b10000001, + 0b11100111, + 0b10000001, + 0b11100111, + 0b11111111, +}, .{ + 0b11100111, + 0b11100111, + 0b11100111, + 0b11111111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b11111111, +}, .{ + 0b11000011, + 0b10011001, + 0b10000111, + 0b11011011, + 0b11100001, + 0b10011001, + 0b11000011, + 0b11111111, +}, .{ + 0b10010011, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11000011, + 0b10111101, + 0b01100110, + 0b01011110, + 0b01011110, + 0b01100110, + 0b10111101, + 0b11000011, +}, .{ + 0b10000111, + 0b11000011, + 0b10010011, + 0b11000011, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11001001, + 0b10010011, + 0b00100111, + 0b10010011, + 0b11001001, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10000001, + 0b11111001, + 0b11111001, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11000011, + 0b10111101, + 0b01000110, + 0b01011010, + 0b01000110, + 0b01011010, + 0b10111101, + 0b11000011, +}, .{ + 0b10000011, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11101111, + 0b11010111, + 0b11101111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11100111, + 0b11100111, + 0b10000001, + 0b11100111, + 0b11100111, + 0b11111111, + 0b10000001, + 0b11111111, +}, .{ + 0b11000111, + 0b11110011, + 0b11100111, + 0b11000011, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11000011, + 0b11100111, + 0b11110011, + 0b11000111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b00110011, + 0b00110011, + 0b00110011, + 0b00110011, + 0b00001001, + 0b00111111, +}, .{ + 0b11000001, + 0b10010101, + 0b10110101, + 0b10010101, + 0b11000001, + 0b11110101, + 0b11110101, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11001111, + 0b11001111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11110111, + 0b11001111, +}, .{ + 0b11100111, + 0b11000111, + 0b11100111, + 0b11000011, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b10010011, + 0b11000111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b00100111, + 0b10010011, + 0b11001001, + 0b10010011, + 0b00100111, + 0b11111111, + 0b11111111, +}, .{ + 0b10111101, + 0b00111011, + 0b10110111, + 0b10101101, + 0b11011001, + 0b10110001, + 0b01111101, + 0b11111111, +}, .{ + 0b10111101, + 0b00111011, + 0b10110111, + 0b10101001, + 0b11011101, + 0b10111011, + 0b01110001, + 0b11111111, +}, .{ + 0b00011101, + 0b10111011, + 0b11010111, + 0b00101101, + 0b11011001, + 0b10110001, + 0b01111101, + 0b11111111, +}, .{ + 0b11000111, + 0b11111111, + 0b11000111, + 0b10011111, + 0b00111001, + 0b00000001, + 0b10000011, + 0b11111111, +}, .{ + 0b11011111, + 0b11101111, + 0b11000111, + 0b10010011, + 0b00111001, + 0b00000001, + 0b00111001, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b11000111, + 0b10010011, + 0b00111001, + 0b00000001, + 0b00111001, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b11000111, + 0b10010011, + 0b00111001, + 0b00000001, + 0b00111001, + 0b11111111, +}, .{ + 0b11001011, + 0b10100111, + 0b11000111, + 0b10010011, + 0b00111001, + 0b00000001, + 0b00111001, + 0b11111111, +}, .{ + 0b10010011, + 0b11111111, + 0b11000111, + 0b10010011, + 0b00111001, + 0b00000001, + 0b00111001, + 0b11111111, +}, .{ + 0b11101111, + 0b11010111, + 0b11000111, + 0b10010011, + 0b00111001, + 0b00000001, + 0b00111001, + 0b11111111, +}, .{ + 0b11000001, + 0b10000111, + 0b00100111, + 0b00100001, + 0b00000111, + 0b00100111, + 0b00100001, + 0b11111111, +}, .{ + 0b11000011, + 0b10011001, + 0b00111111, + 0b00111111, + 0b10011001, + 0b11000011, + 0b11110111, + 0b11001111, +}, .{ + 0b11011111, + 0b11101111, + 0b00000001, + 0b00111111, + 0b00000011, + 0b00111111, + 0b00000001, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b00000001, + 0b00111111, + 0b00000011, + 0b00111111, + 0b00000001, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b00000001, + 0b00111111, + 0b00000011, + 0b00111111, + 0b00000001, + 0b11111111, +}, .{ + 0b10010011, + 0b11111111, + 0b00000001, + 0b00111111, + 0b00000011, + 0b00111111, + 0b00000001, + 0b11111111, +}, .{ + 0b11101111, + 0b11110111, + 0b10000001, + 0b11100111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b10000001, + 0b11100111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b11100111, + 0b11000011, + 0b10000001, + 0b11100111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b10011001, + 0b11111111, + 0b10000001, + 0b11100111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b10000111, + 0b10010011, + 0b10011001, + 0b00001001, + 0b10011001, + 0b10010011, + 0b10000111, + 0b11111111, +}, .{ + 0b11001011, + 0b10100111, + 0b00011001, + 0b00001001, + 0b00000001, + 0b00100001, + 0b00110001, + 0b11111111, +}, .{ + 0b11011111, + 0b11101111, + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11001011, + 0b10100111, + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b10010011, + 0b11111111, + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11111111, + 0b10111011, + 0b11010111, + 0b11101111, + 0b11010111, + 0b10111011, + 0b11111111, + 0b11111111, +}, .{ + 0b10000011, + 0b00111001, + 0b00110001, + 0b00101001, + 0b00011001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11011111, + 0b11101111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b11111111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b10010011, + 0b11111111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b10011001, + 0b10011001, + 0b11000011, + 0b11100111, + 0b11100111, + 0b11111111, +}, .{ + 0b00111111, + 0b00000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00000011, + 0b00111111, + 0b11111111, +}, .{ + 0b11000011, + 0b10011001, + 0b10011001, + 0b10010011, + 0b10011001, + 0b10001001, + 0b10010011, + 0b11111111, +}, .{ + 0b11011111, + 0b11101111, + 0b10000011, + 0b11111001, + 0b10000001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b10000011, + 0b11111001, + 0b10000001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b10000011, + 0b11111001, + 0b10000001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b11001011, + 0b10100111, + 0b10000011, + 0b11111001, + 0b10000001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b10010011, + 0b11111111, + 0b10000011, + 0b11111001, + 0b10000001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b11101111, + 0b11010111, + 0b10000011, + 0b11111001, + 0b10000001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10000011, + 0b11101001, + 0b10000001, + 0b00101111, + 0b10000011, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10000001, + 0b00111111, + 0b00111111, + 0b10000001, + 0b11110111, + 0b11001111, +}, .{ + 0b11011111, + 0b11101111, + 0b10000011, + 0b00111001, + 0b00000001, + 0b00111111, + 0b10000011, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b10000011, + 0b00111001, + 0b00000001, + 0b00111111, + 0b10000011, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b10000011, + 0b00111001, + 0b00000001, + 0b00111111, + 0b10000011, + 0b11111111, +}, .{ + 0b10010011, + 0b11111111, + 0b10000011, + 0b00111001, + 0b00000001, + 0b00111111, + 0b10000011, + 0b11111111, +}, .{ + 0b11011111, + 0b11101111, + 0b11111111, + 0b11000111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b11111111, + 0b11000111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b11111111, + 0b11000111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b10010011, + 0b11111111, + 0b11000111, + 0b11100111, + 0b11100111, + 0b11100111, + 0b10000001, + 0b11111111, +}, .{ + 0b10011011, + 0b10000111, + 0b01100111, + 0b10000011, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11001011, + 0b10100111, + 0b00000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b11111111, +}, .{ + 0b11011111, + 0b11101111, + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11001011, + 0b10100111, + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b10010011, + 0b11111111, + 0b10000011, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000011, + 0b11111111, +}, .{ + 0b11111111, + 0b11100111, + 0b11111111, + 0b10000001, + 0b11111111, + 0b11100111, + 0b11111111, + 0b11111111, +}, .{ + 0b11111111, + 0b11111111, + 0b10000011, + 0b00110001, + 0b00101001, + 0b00011001, + 0b10000011, + 0b11111111, +}, .{ + 0b11011111, + 0b11101111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b11000111, + 0b10010011, + 0b11111111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b10010011, + 0b11111111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111111, +}, .{ + 0b11110111, + 0b11101111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111001, + 0b10000011, +}, .{ + 0b00111111, + 0b00111111, + 0b00000011, + 0b00111001, + 0b00111001, + 0b00000011, + 0b00111111, + 0b00111111, +}, .{ + 0b10010011, + 0b11111111, + 0b00111001, + 0b00111001, + 0b00111001, + 0b10000001, + 0b11111001, + 0b10000011, +} }; + +const audio = @import("audio.zig"); +const lcd = @import("lcd.zig"); +const options = @import("options"); +const Port = @import("Port.zig"); +const std = @import("std"); +const timer = @import("timer.zig"); +const utils = @import("utils.zig"); diff --git a/src/dma.zig b/src/dma.zig index f9a5c29..ca68379 100644 --- a/src/dma.zig +++ b/src/dma.zig @@ -1,6 +1,8 @@ pub const enable = true; pub fn initLcd(bpp: lcd.Bpp) void { + @setCold(true); + init(); io.DMAC.CHANNEL[CHANNEL.LCD].CHCTRLA.write(.{ .SWRST = 0, @@ -80,6 +82,8 @@ pub fn stopLcd() void { } pub fn initAudio() void { + @setCold(true); + init(); io.DMAC.CHANNEL[CHANNEL.AUDIO].CHCTRLA.write(.{ .SWRST = 0, @@ -185,6 +189,8 @@ pub fn waitAudio(i: usize) void { } fn init() void { + @setCold(true); + if (initialized) return; io.MCLK.AHBMASK.modify(.{ .DMAC_ = 1 }); io.DMAC.CTRL.write(.{ diff --git a/src/lcd.zig b/src/lcd.zig index f852f82..a86c02d 100644 --- a/src/lcd.zig +++ b/src/lcd.zig @@ -7,12 +7,12 @@ pub const FrameBuffer = union { }; pub const Bpp = std.meta.FieldEnum(FrameBuffer); pub const Color12 = extern struct { - r0_g0: packed struct(u8) { r0: u4, g0: u4 }, - b0_r1: packed struct(u8) { b0: u4, r1: u4 }, - g1_b1: packed struct(u8) { g1: u4, b1: u4 }, + b0_g0: packed struct(u8) { b0: u4, g0: u4 }, + r0_b1: packed struct(u8) { r0: u4, b1: u4 }, + g1_r1: packed struct(u8) { g1: u4, r1: u4 }, }; -pub const Color16 = packed struct(u16) { r: u5, g: u6, b: u5 }; -pub const Color24 = extern struct { r: u8, g: u8, b: u8 }; +pub const Color16 = packed struct(u16) { b: u5, g: u6, r: u5 }; +pub const Color24 = extern struct { b: u8, g: u8, r: u8 }; pub const Rect = struct { x: u8, y: u8, width: u8, height: u8 }; pub const width = 160; @@ -31,6 +31,8 @@ pub const blue24: Color24 = .{ .r = 0x00, .g = 0x00, .b = 0xff }; pub const white24: Color24 = .{ .r = 0xff, .g = 0xff, .b = 0xff }; pub fn init(bpp: Bpp) void { + @setCold(true); + Port.TFT_RST.setDir(.out); Port.TFT_LITE.setDir(.out); Port.TFT_DC.setDir(.out); @@ -38,15 +40,15 @@ pub fn init(bpp: Bpp) void { Port.TFT_SCK.setDir(.out); Port.TFT_MOSI.setDir(.out); - Port.TFT_CS.write(true); - Port.TFT_DC.write(true); - Port.TFT_SCK.write(true); - Port.TFT_MOSI.write(true); + Port.TFT_CS.write(.high); + Port.TFT_DC.write(.high); + Port.TFT_SCK.write(.high); + Port.TFT_MOSI.write(.high); - Port.TFT_LITE.write(true); - Port.TFT_RST.write(false); + Port.TFT_LITE.write(.high); + Port.TFT_RST.write(.low); timer.delay(20 * std.time.ms_per_s); - Port.TFT_RST.write(true); + Port.TFT_RST.write(.high); timer.delay(20 * std.time.ms_per_s); io.MCLK.APBDMASK.modify(.{ .SERCOM4_ = 1 }); @@ -109,11 +111,11 @@ pub fn init(bpp: Bpp) void { }))}, 1); sendCmd(ST7735.MADCTL, &.{@as(u8, @bitCast(ST7735.MADCTL_PARAM0{ .MH = .LEFT_TO_RIGHT, - .RGB = .RGB, + .RGB = .BGR, .ML = .TOP_TO_BOTTOM, .MV = false, .MX = false, - .MY = false, + .MY = true, }))}, 1); sendCmd(ST7735.GMCTRP1, &.{ 0x02, 0x1c, 0x07, 0x12, @@ -126,7 +128,7 @@ pub fn init(bpp: Bpp) void { sendCmd(ST7735.RAMWR, &.{}, 1); if (dma.enable) { - Port.TFT_CS.write(false); + Port.TFT_CS.write(.low); timer.delay(1); dma.initLcd(bpp); } @@ -192,27 +194,27 @@ pub fn blit24() void { fn sendCmd(cmd: u8, params: []const u8, delay_us: u32) void { timer.delay(1); - Port.TFT_CS.write(false); - Port.TFT_DC.write(false); + Port.TFT_CS.write(.low); + Port.TFT_DC.write(.low); timer.delay(1); io.SERCOM4.SPIM.DATA.write(.{ .DATA = cmd }); while (io.SERCOM4.SPIM.INTFLAG.read().TXC == 0) {} timer.delay(1); - Port.TFT_DC.write(true); + Port.TFT_DC.write(.high); for (params) |param| { while (io.SERCOM4.SPIM.INTFLAG.read().DRE == 0) {} io.SERCOM4.SPIM.DATA.write(.{ .DATA = param }); } while (io.SERCOM4.SPIM.INTFLAG.read().TXC == 0) {} timer.delay(1); - Port.TFT_CS.write(true); + Port.TFT_CS.write(.high); timer.delay(delay_us); } fn start() void { if (dma.enable) { sendCmd(ST7735.RAMWR, &.{}, 1); - Port.TFT_CS.write(false); + Port.TFT_CS.write(.low); timer.delay(1); dma.startLcd(); } @@ -222,7 +224,7 @@ fn stop() void { if (dma.enable) { dma.stopLcd(); timer.delay(1); - Port.TFT_CS.write(true); + Port.TFT_CS.write(.high); timer.delay(1); } } diff --git a/src/main.zig b/src/main.zig index 59ab389..ba7d368 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,11 +1,13 @@ const audio = @import("audio.zig"); const builtin = @import("builtin"); +const cart = @import("cart.zig"); const GCLK = @import("chip.zig").GCLK; const io = microzig.chip.peripherals; const io_types = microzig.chip.types.peripherals; -const microzig = @import("microzig"); const lcd = @import("lcd.zig"); +const microzig = @import("microzig"); const NVMCTRL = @import("chip.zig").NVMCTRL; +const options = @import("options"); const Port = @import("Port.zig"); const sleep = microzig.core.experimental.debug.busy_sleep; const std = @import("std"); @@ -23,37 +25,150 @@ pub const std_options = struct { pub const microzig_options = struct { pub const interrupts = struct { const interrupt_log = std.log.scoped(.interrupt); + const Context = extern struct { + R0: u32, + R1: u32, + R2: u32, + R3: u32, + R12: u32, + LR: u32, + ReturnAddress: u32, + xPSR: u32, + }; pub const NonMaskableInt = unhandled("NonMaskableInt"); - pub fn HardFault() void { - interrupt_log.info("[HardFault] HFSR = 0x{x}", .{io.SystemControl.HFSR.raw}); - microzig.hang(); - } - pub fn MemoryManagement() void { - interrupt_log.info("[MemoryManagement] CFSR = 0x{x}, MMFAR = 0x{x}", .{ - io.SystemControl.CFSR.raw, - io.SystemControl.MMFAR.raw, - }); - microzig.hang(); - } - pub fn BusFault() void { - interrupt_log.info("[BusFault] CFSR = 0x{x}, BFAR = 0x{x}", .{ - io.SystemControl.CFSR.raw, - io.SystemControl.BFAR.raw, - }); - microzig.hang(); - } - pub fn UsageFault() void { - interrupt_log.info("[UsageFault] CFSR = 0x{x}", .{io.SystemControl.CFSR.raw}); - microzig.hang(); + pub const HardFault = withContext(struct { + fn handler(ctx: *Context) void { + interrupt_log.info("[HardFault] HFSR = 0x{x}, ReturnAddress = 0x{x}", .{ + io.SystemControl.HFSR.raw, + ctx.ReturnAddress, + }); + microzig.hang(); + } + }.handler); + pub const MemoryManagement = withContext(struct { + fn handler(ctx: *Context) void { + interrupt_log.info("[MemoryManagement] CFSR = 0x{x}, MMFAR = 0x{x}, ReturnAddress = 0x{x}", .{ + io.SystemControl.CFSR.raw, + io.SystemControl.MMFAR.raw, + ctx.ReturnAddress, + }); + microzig.hang(); + } + }.handler); + pub const BusFault = withContext(struct { + fn handler(ctx: *Context) void { + interrupt_log.info("[BusFault] CFSR = 0x{x}, BFAR = 0x{x}, ReturnAddress = 0x{x}", .{ + io.SystemControl.CFSR.raw, + io.SystemControl.BFAR.raw, + ctx.ReturnAddress, + }); + microzig.hang(); + } + }.handler); + pub const UsageFault = withContext(struct { + fn handler(ctx: *Context) void { + interrupt_log.info("[UsageFault] CFSR = 0x{x}, ReturnAddress = 0x{x}", .{ + io.SystemControl.CFSR.raw, + ctx.ReturnAddress, + }); + microzig.hang(); + } + }.handler); + pub fn SVCall() callconv(.Naked) void { + asm volatile ( + \\ mvns r0, lr, lsl #31 - 2 + \\ bcc 1f + \\ ite mi + \\ movmi r1, sp + \\ mrspl r1, psp + \\ ldr r2, [r1, #6 * 4] + \\ subs r2, #2 + \\ ldrb r3, [r2, #1 * 1] + \\ cmp r3, #0xDF + \\ bne 1f + \\ ldrb r3, [r2, #0 * 1] + \\ cmp r3, #12 + \\ bhi 1f + \\ tbb [pc, r3] + \\0: + \\ .byte (0f - 0b) / 2 + \\ .byte (9f - 0b) / 2 + \\ .byte (9f - 0b) / 2 + \\ .byte (2f - 0b) / 2 + \\ .byte (3f - 0b) / 2 + \\ .byte (4f - 0b) / 2 + \\ .byte (5f - 0b) / 2 + \\ .byte (6f - 0b) / 2 + \\ .byte (7f - 0b) / 2 + \\ .byte (8f - 0b) / 2 + \\ .byte (8f - 0b) / 2 + \\ .byte (10f - 0b) / 2 + \\1: + \\ .byte (11f - 0b) / 2 + \\ .byte 0xDE + \\ .align 1 + \\0: + \\ ldm r1, {r0-r3} + \\ b %[blit:P] + \\2: + \\ ldm r1, {r0-r3} + \\ b %[oval:P] + \\3: + \\ ldm r1, {r0-r3} + \\ b %[rect:P] + \\4: + \\ ldm r1, {r0-r3} + \\ b %[text:P] + \\5: + \\ ldm r1, {r0-r2} + \\ b %[vline:P] + \\6: + \\ ldm r1, {r0-r2} + \\ b %[hline:P] + \\7: + \\ ldm r1, {r0-r3} + \\ b %[tone:P] + \\8: + \\ movs r0, #0 + \\ str r0, [r1, #0 * 4] + \\9: + \\ bx lr + \\10: + \\ ldm r1, {r0-r1} + \\ b %[trace:P] + \\11: + \\ lsrs r0, #31 + \\ msr control, r0 + \\ it eq + \\ popeq {r3, r5-r11, pc} + \\ subs r0, #1 - 0xFFFFFFFD + \\ push {r4-r11, lr} + \\ movs r4, #0 + \\ movs r5, #0 + \\ movs r6, #0 + \\ movs r7, #0 + \\ mov r8, r4 + \\ mov r9, r5 + \\ mov r10, r6 + \\ mov r11, r7 + \\ bx r0 + : + : [blit] "X" (&cart.blit), + [oval] "X" (&cart.oval), + [rect] "X" (&cart.rect), + [text] "X" (&cart.text), + [vline] "X" (&cart.vline), + [hline] "X" (&cart.hline), + [tone] "X" (&cart.tone), + [trace] "X" (&cart.trace), + ); } - pub const SVCall = unhandled("SVCall"); pub const DebugMonitor = unhandled("DebugMonitor"); pub const PendSV = unhandled("PendSV"); pub const SysTick = unhandled("SysTick"); - pub fn DMAC_DMAC_1() void { - audio.mix(); - } + pub const DMAC_DMAC_1 = audio.mix; + fn unhandled(comptime name: []const u8) fn () callconv(.C) void { return struct { fn handler() callconv(.C) void { @@ -62,6 +177,21 @@ pub const microzig_options = struct { } }.handler; } + fn withContext(comptime handler: fn (*Context) void) fn () callconv(.Naked) void { + return struct { + fn interrupt() callconv(.Naked) void { + asm volatile ( + \\ tst lr, #1 << 2 + \\ ite eq + \\ moveq r0, sp + \\ mrsne r0, psp + \\ b %[handler:P] + : + : [handler] "X" (&handler), + ); + } + }.interrupt; + } }; }; @@ -115,6 +245,17 @@ pub fn dumpPeripheral(logger: anytype, comptime prefix: []const u8, pointer: any } pub fn main() !void { + io.SystemControl.CCR.modify(.{ + .NONBASETHRDENA = 0, + .USERSETMPEND = 0, + .reserved3 = 0, + .UNALIGN_TRP = .{ .value = .VALUE_0 }, // TODO + .DIV_0_TRP = 1, + .reserved8 = 0, + .BFHFNMIGN = 0, + .STKALIGN = .{ .value = .VALUE_1 }, + .padding = 0, + }); io.SystemControl.SHCSR.modify(.{ .MEMFAULTENA = 1, .BUSFAULTENA = 1, @@ -122,8 +263,8 @@ pub fn main() !void { }); io.SystemControl.CPACR.write(.{ .reserved20 = 0, - .CP10 = .{ .value = .PRIV }, - .CP11 = .{ .value = .PRIV }, + .CP10 = .{ .value = .FULL }, + .CP11 = .{ .value = .FULL }, .padding = 0, }); @@ -144,7 +285,7 @@ pub fn main() !void { // ID detection const id_port = Port.D13; id_port.setDir(.out); - id_port.write(true); + id_port.write(.high); id_port.configPtr().write(.{ .PMUXEN = 0, .INEN = 1, @@ -159,23 +300,77 @@ pub fn main() !void { lcd.init(.bpp24); audio.init(); - // button testing - Port.BUTTON_OUT.setDir(.in); - Port.BUTTON_OUT.configPtr().write(.{ - .PMUXEN = 0, - .INEN = 1, - .PULLEN = 0, - .reserved6 = 0, - .DRVSTR = 0, + io.MPU.RBAR.write(.{ + .REGION = 0, + .VALID = 1, + .ADDR = @intFromPtr(utils.FLASH.ADDR) >> 5, + }); + io.MPU.RASR.write(.{ + .ENABLE = 1, + .SIZE = (@ctz(utils.FLASH.SIZE) - 1) & 1, + .reserved8 = @as(u4, (@ctz(utils.FLASH.SIZE) - 1) >> 1), + .SRD = if (options.have_cart) 0b00000111 else 0b00000000, + .B = 0, + .C = 1, + .S = 0, + .TEX = 0b000, + .reserved24 = 0, + .AP = 0b010, + .reserved28 = 0, + .XN = 0, + .padding = 0, + }); + io.MPU.RBAR_A1.write(.{ + .REGION = 1, + .VALID = 1, + .ADDR = @intFromPtr(utils.HSRAM.ADDR) >> 5, + }); + io.MPU.RASR_A1.write(.{ + .ENABLE = 1, + .SIZE = (@ctz(@divExact(utils.HSRAM.SIZE, 3) * 2) - 1) & 1, + .reserved8 = @as(u4, (@ctz(@divExact(utils.HSRAM.SIZE, 3) * 2) - 1) >> 1), + .SRD = if (options.have_cart) 0b11110000 else 0b00000000, + .B = 1, + .C = 1, + .S = 0, + .TEX = 0b001, + .reserved24 = 0, + .AP = 0b011, + .reserved28 = 0, + .XN = 1, + .padding = 0, + }); + io.MPU.RBAR_A2.write(.{ + .REGION = 2, + .VALID = 1, + .ADDR = @intFromPtr(utils.HSRAM.ADDR[@divExact(utils.HSRAM.SIZE, 3) * 2 ..]) >> 5, + }); + io.MPU.RASR_A2.write(.{ + .ENABLE = 1, + .SIZE = (@ctz(@divExact(utils.HSRAM.SIZE, 3)) - 1) & 1, + .reserved8 = @as(u4, (@ctz(@divExact(utils.HSRAM.SIZE, 3)) - 1) >> 1), + .SRD = 0b11001111, + .B = 1, + .C = 1, + .S = 0, + .TEX = 0b001, + .reserved24 = 0, + .AP = 0b011, + .reserved28 = 0, + .XN = 1, + .padding = 0, + }); + io.MPU.CTRL.write(.{ + .ENABLE = 1, + .HFNMIENA = 0, + .PRIVDEFENA = 1, .padding = 0, }); - Port.BUTTON_CLK.setDir(.out); - Port.BUTTON_CLK.write(true); - Port.BUTTON_LATCH.setDir(.out); - Port.BUTTON_LATCH.write(false); - //io.MCLK.APBAMASK.modify(.{ .EIC_ = 1 }); + + cart.init(); var was_ready = false; + var cart_running = false; var color: enum { red, green, blue } = .red; while (true) { usb.tick(); @@ -190,14 +385,16 @@ pub fn main() !void { }]; if (!was_ready) { std.log.info("Ready!", .{}); + cart.start(); was_ready = true; + cart_running = true; } if (data.len > 0) for (data) |c| switch (c) { else => out.writeByte(c) catch break :input, 'A' - '@' => { timer.delay(std.time.us_per_s); const tempo = 0.78; - audio.play(&.{ + audio.playSong(&.{ &.{ .{ .duration = tempo * 0.75, .frequency = audio.Note.F5 }, .{ .duration = tempo * 0.25, .frequency = audio.Note.Db5 }, @@ -493,9 +690,12 @@ pub fn main() !void { } timer_log.info("done!", .{}); }, + 'U' - '@' => cart_running = !cart_running, 0x7f => out.writeAll("\x1B[D\x1B[K") catch break :input, }; } + + if (cart_running) cart.tick(); } } @@ -655,6 +855,8 @@ pub const usb = struct { /// One-time initialization fn init() void { + @setCold(true); + Port.@"D-".setMux(.H); Port.@"D+".setMux(.H); io.MCLK.AHBMASK.modify(.{ .USB_ = 1 }); @@ -663,6 +865,8 @@ pub const usb = struct { /// Reinitialize into the specified mode fn reinitMode(mode: io_types.USB.USB_CTRLA__MODE) void { + @setCold(true); + // Tear down clocks io.GCLK.GENCTRL[GCLK.GEN.@"120MHz".ID].write(.{ .SRC = .{ .value = .OSCULP32K }, diff --git a/src/timer.zig b/src/timer.zig index 2a4f090..4525a38 100644 --- a/src/timer.zig +++ b/src/timer.zig @@ -1,4 +1,6 @@ pub fn init() void { + @setCold(true); + io.MCLK.APBAMASK.modify(.{ .TC0_ = 1, .TC1_ = 1 }); io.TC0.COUNT32.CTRLA.write(.{ .SWRST = 0, @@ -156,6 +158,128 @@ pub fn delay(us: u32) void { finishDelay(); } +pub fn initFrameSync() void { + io.MCLK.APBCMASK.modify(.{ .TC4_ = 1 }); + io.TC4.COUNT16.CTRLA.write(.{ + .SWRST = 0, + .ENABLE = 0, + .MODE = .{ .raw = 0 }, + .PRESCSYNC = .{ .raw = 0 }, + .RUNSTDBY = 0, + .ONDEMAND = 0, + .PRESCALER = .{ .raw = 0 }, + .ALOCK = 0, + .reserved16 = 0, + .CAPTEN0 = 0, + .CAPTEN1 = 0, + .reserved20 = 0, + .COPEN0 = 0, + .COPEN1 = 0, + .reserved24 = 0, + .CAPTMODE0 = .{ .raw = 0 }, + .reserved27 = 0, + .CAPTMODE1 = .{ .raw = 0 }, + .padding = 0, + }); + while (io.TC4.COUNT16.SYNCBUSY.read().ENABLE != 0) {} + io.GCLK.PCHCTRL[GCLK.PCH.TC4].write(.{ + .GEN = .{ .value = GCLK.GEN.@"8.4672MHz".PCHCTRL_GEN }, + .reserved6 = 0, + .CHEN = 1, + .WRTLOCK = 0, + .padding = 0, + }); + io.TC4.COUNT16.CTRLA.write(.{ + .SWRST = 1, + .ENABLE = 0, + .MODE = .{ .raw = 0 }, + .PRESCSYNC = .{ .raw = 0 }, + .RUNSTDBY = 0, + .ONDEMAND = 0, + .PRESCALER = .{ .raw = 0 }, + .ALOCK = 0, + .reserved16 = 0, + .CAPTEN0 = 0, + .CAPTEN1 = 0, + .reserved20 = 0, + .COPEN0 = 0, + .COPEN1 = 0, + .reserved24 = 0, + .CAPTMODE0 = .{ .raw = 0 }, + .reserved27 = 0, + .CAPTMODE1 = .{ .raw = 0 }, + .padding = 0, + }); + while (io.TC4.COUNT16.SYNCBUSY.read().SWRST != 0) {} + io.TC4.COUNT16.CTRLA.write(.{ + .SWRST = 0, + .ENABLE = 0, + .MODE = .{ .value = .COUNT16 }, + .PRESCSYNC = .{ .value = .PRESC }, + .RUNSTDBY = 0, + .ONDEMAND = 0, + .PRESCALER = .{ .value = .DIV64 }, + .ALOCK = 0, + .reserved16 = 0, + .CAPTEN0 = 0, + .CAPTEN1 = 0, + .reserved20 = 0, + .COPEN0 = 0, + .COPEN1 = 0, + .reserved24 = 0, + .CAPTMODE0 = .{ .raw = 0 }, + .reserved27 = 0, + .CAPTMODE1 = .{ .raw = 0 }, + .padding = 0, + }); + io.TC4.COUNT16.WAVE.write(.{ .WAVEGEN = .{ .value = .MFRQ }, .padding = 0 }); + io.TC4.COUNT16.CC[0].write(.{ .CC = @divExact(8_467_200, 64 * 60) - 1 }); + while (io.TC4.COUNT16.SYNCBUSY.read().CC0 != 0) {} + io.TC4.COUNT16.CTRLA.write(.{ + .SWRST = 0, + .ENABLE = 1, + .MODE = .{ .value = .COUNT16 }, + .PRESCSYNC = .{ .value = .PRESC }, + .RUNSTDBY = 0, + .ONDEMAND = 0, + .PRESCALER = .{ .value = .DIV64 }, + .ALOCK = 0, + .reserved16 = 0, + .CAPTEN0 = 0, + .CAPTEN1 = 0, + .reserved20 = 0, + .COPEN0 = 0, + .COPEN1 = 0, + .reserved24 = 0, + .CAPTMODE0 = .{ .raw = 0 }, + .reserved27 = 0, + .CAPTMODE1 = .{ .raw = 0 }, + .padding = 0, + }); + while (io.TC4.COUNT16.SYNCBUSY.read().ENABLE != 0) {} + io.TC4.COUNT16.CTRLBSET.write(.{ + .DIR = 0, + .LUPD = 0, + .ONESHOT = 0, + .reserved5 = 0, + .CMD = .{ .value = .RETRIGGER }, + }); + while (io.TC4.COUNT16.SYNCBUSY.read().CTRLB != 0) {} +} + +pub fn checkFrameReady() bool { + if (io.TC4.COUNT16.INTFLAG.read().OVF != 1) return false; + io.TC4.COUNT16.INTFLAG.write(.{ + .OVF = 1, + .ERR = 0, + .reserved4 = 0, + .MC0 = 0, + .MC1 = 0, + .padding = 0, + }); + return true; +} + const GCLK = @import("chip.zig").GCLK; const io = microzig.chip.peripherals; const microzig = @import("microzig"); diff --git a/src/utils.zig b/src/utils.zig index 107fe87..14da2da 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -14,9 +14,16 @@ fn NVIC_SystemReset() noreturn { } //adafruit/uf2-samdx1:lib/samd51/include/samd51j19a.h -const HSRAM = struct { - const ADDR: *align(4) [SIZE]u8 = @ptrFromInt(0x20000000); - const SIZE = 0x00030000; +pub const HSRAM = struct { + pub const SIZE: usize = 0x00030000; // 192 kB + pub const ADDR: *align(SIZE / 3) volatile [SIZE]u8 = @ptrFromInt(0x20000000); +}; +pub const FLASH = struct { + pub const SIZE: usize = 0x00080000; // 512 kB + pub const PAGE_SIZE = 512; + pub const NB_OF_PAGES = 1024; + pub const USER_PAGE_SIZE = 512; + pub const ADDR: *allowzero align(PAGE_SIZE) volatile [SIZE]u8 = @ptrFromInt(0x00000000); }; //adafruit/uf2-samdx1:inc/uf2.h diff --git a/src/wasm4.zig b/src/wasm4.zig new file mode 100644 index 0000000..3c9c4ff --- /dev/null +++ b/src/wasm4.zig @@ -0,0 +1,251 @@ +// +// WASM-4: https://wasm4.org/docs + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Platform Constants │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +pub const SCREEN_SIZE: u32 = 160; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Memory Addresses │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +pub const PALETTE: *[4]u32 = @ptrFromInt(0x20000004); +pub const DRAW_COLORS: *u16 = @ptrFromInt(0x20000014); +pub const GAMEPAD1: *const u8 = @ptrFromInt(0x20000016); +pub const GAMEPAD2: *const u8 = @ptrFromInt(0x20000017); +pub const GAMEPAD3: *const u8 = @ptrFromInt(0x20000018); +pub const GAMEPAD4: *const u8 = @ptrFromInt(0x20000019); +pub const MOUSE_X: *const i16 = @ptrFromInt(0x2000001a); +pub const MOUSE_Y: *const i16 = @ptrFromInt(0x2000001c); +pub const MOUSE_BUTTONS: *const u8 = @ptrFromInt(0x2000001e); +pub const SYSTEM_FLAGS: *u8 = @ptrFromInt(0x2000001f); +pub const NETPLAY: *const u8 = @ptrFromInt(0x20000020); +pub const FRAMEBUFFER: *[6400]u8 = @ptrFromInt(0x200000A0); + +pub const BUTTON_1: u8 = 1; +pub const BUTTON_2: u8 = 2; +pub const BUTTON_LEFT: u8 = 16; +pub const BUTTON_RIGHT: u8 = 32; +pub const BUTTON_UP: u8 = 64; +pub const BUTTON_DOWN: u8 = 128; + +pub const MOUSE_LEFT: u8 = 1; +pub const MOUSE_RIGHT: u8 = 2; +pub const MOUSE_MIDDLE: u8 = 4; + +pub const SYSTEM_PRESERVE_FRAMEBUFFER: u8 = 1; +pub const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Drawing Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Copies pixels to the framebuffer. +pub inline fn blit(sprite: [*]const u8, x: i32, y: i32, width: u32, height: u32, flags: u32) void { + const rest: extern struct { + width: u32, + height: u32, + flags: u32, + } = .{ + .width = width, + .height = height, + .flags = flags, + }; + asm volatile (" svc #0" + : + : [sprite] "{r0}" (sprite), + [x] "{r1}" (x), + [y] "{r2}" (y), + [rest] "{r3}" (&rest), + : "memory" + ); +} + +/// Copies a subregion within a larger sprite atlas to the framebuffer. +pub inline fn blitSub(sprite: [*]const u8, x: i32, y: i32, width: u32, height: u32, src_x: u32, src_y: u32, stride: u32, flags: u32) void { + const rest: extern struct { + width: u32, + height: u32, + src_x: u32, + src_y: u32, + stride: u32, + flags: u32, + } = .{ + .width = width, + .height = height, + .src_x = src_x, + .src_y = src_y, + .stride = stride, + .flags = flags, + }; + asm volatile (" svc #1" + : + : [sprite] "{r0}" (sprite), + [x] "{r1}" (x), + [y] "{r2}" (y), + [rest] "{r3}" (&rest), + : "memory" + ); +} + +pub const BLIT_2BPP: u32 = 1; +pub const BLIT_1BPP: u32 = 0; +pub const BLIT_FLIP_X: u32 = 2; +pub const BLIT_FLIP_Y: u32 = 4; +pub const BLIT_ROTATE: u32 = 8; + +/// Draws a line between two points. +pub inline fn line(x1: i32, y1: i32, x2: i32, y2: i32) void { + asm volatile (" svc #2" + : + : [x1] "{r0}" (x1), + [y1] "{r1}" (y1), + [x2] "{r2}" (x2), + [y2] "{r3}" (y2), + : "memory" + ); +} + +/// Draws an oval (or circle). +pub inline fn oval(x: i32, y: i32, width: u32, height: u32) void { + asm volatile (" svc #3" + : + : [x] "{r0}" (x), + [y] "{r1}" (y), + [width] "{r2}" (width), + [height] "{r3}" (height), + : "memory" + ); +} + +/// Draws a rectangle. +pub inline fn rect(x: i32, y: i32, width: u32, height: u32) void { + asm volatile (" svc #4" + : + : [x] "{r0}" (x), + [y] "{r1}" (y), + [width] "{r2}" (width), + [height] "{r3}" (height), + : "memory" + ); +} + +/// Draws text using the built-in system font. +pub inline fn text(str: []const u8, x: i32, y: i32) void { + asm volatile (" svc #5" + : + : [str_ptr] "{r0}" (str.ptr), + [str_len] "{r1}" (str.len), + [x] "{r2}" (x), + [y] "{r3}" (y), + : "memory" + ); +} + +/// Draws a vertical line +pub inline fn vline(x: i32, y: i32, len: u32) void { + asm volatile (" svc #6" + : + : [x] "{r0}" (x), + [y] "{r1}" (y), + [len] "{r2}" (len), + : "memory" + ); +} + +/// Draws a horizontal line +pub inline fn hline(x: i32, y: i32, len: u32) void { + asm volatile (" svc #7" + : + : [x] "{r0}" (x), + [y] "{r1}" (y), + [len] "{r2}" (len), + : "memory" + ); +} + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Sound Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Plays a sound tone. +pub inline fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) void { + asm volatile (" svc #8" + : + : [frequency] "{r0}" (frequency), + [duration] "{r1}" (duration), + [volume] "{r2}" (volume), + [flags] "{r3}" (flags), + ); +} + +pub const TONE_PULSE1: u32 = 0; +pub const TONE_PULSE2: u32 = 1; +pub const TONE_TRIANGLE: u32 = 2; +pub const TONE_NOISE: u32 = 3; +pub const TONE_MODE1: u32 = 0; +pub const TONE_MODE2: u32 = 4; +pub const TONE_MODE3: u32 = 8; +pub const TONE_MODE4: u32 = 12; +pub const TONE_PAN_LEFT: u32 = 16; +pub const TONE_PAN_RIGHT: u32 = 32; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Storage Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Reads up to `size` bytes from persistent storage into the pointer `dest`. +pub inline fn diskr(dest: [*]u8, size: u32) u32 { + return asm volatile (" svc #9" + : [result] "={r0}" (-> u32), + : [dest] "{r0}" (dest), + [size] "{r1}" (size), + ); +} + +/// Writes up to `size` bytes from the pointer `src` into persistent storage. +pub inline fn diskw(src: [*]const u8, size: u32) u32 { + return asm volatile (" svc #10" + : [result] "={r0}" (-> u32), + : [src] "{r0}" (src), + [size] "{r1}" (size), + ); +} + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Other Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Prints a message to the debug console. +pub inline fn trace(x: []const u8) void { + asm volatile (" svc #11" + : + : [x_ptr] "{r0}" (x.ptr), + [x_len] "{r1}" (x.len), + ); +} + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Internal Use │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +pub export fn __return_thunk__() noreturn { + asm volatile (" svc #12"); + unreachable; +}