summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig104
1 files changed, 104 insertions, 0 deletions
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..2e65060
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,104 @@
+//! Demo application: prove the toolchain, then blink.
+//!
+//! Nothing here is framework code. There is no FreeRTOS, no `app_main`, no libc and no
+//! `compiler_rt`: `_start` is the reset entry the bootloader jumps to, the peripherals are the
+//! comptime register model in soc.zig, and `printf` is a mask-ROM address.
+
+const std = @import("std");
+const config = @import("config");
+const soc = @import("soc");
+
+const led: u6 = @intCast(config.led_pin);
+
+/// Zero-initialised statics live in L2MEM, which the bootloader does not clear. `_start` clears
+/// them, and this array exists so the board can prove it happened.
+export var bss_probe: [64]u32 = @splat(0);
+
+export fn zig_main() noreturn {
+ // readback: enable the pad's input buffer too, so the pin can be sampled while it is driven -
+ // which is what makes the blink self-verifying rather than hopeful.
+ soc.gpio.configureOutput(led, .{ .readback = true });
+
+ soc.rom.print("\r\nMARK ZIG_P4 toolchain=zig-only, no cmake, no esptool, no external linker\r\n", .{});
+ soc.rom.print("MARK ZIG_P4_ROM ets_printf@0x%x reached from zig\r\n", .{@as(u32, @intFromPtr(&soc.rom.ets_printf))});
+
+ var bss_or: u32 = 0;
+ for (&bss_probe) |*w| bss_or |= @as(*volatile u32, w).*;
+ soc.rom.print("MARK ZIG_P4_BSS or-of-64-words=0x%08x expect=0x00000000\r\n", .{bss_or});
+
+ // A comptime hash of a compile-time string, checked against a value computed on the host:
+ // if the ABI or the linker script were wrong, this would not match.
+ const fnv = comptime fnv1a("0x4200.cafe");
+ soc.rom.print("MARK ZIG_P4_FNV fnv1a(0x4200.cafe)=0x%08x expect=0x68440dea\r\n", .{fnv});
+
+ // A float the compiler cannot fold: loaded through a volatile pointer, so the arithmetic really
+ // happens at run time as fcvt.s.wu/fmul.s/fadd.s. Without the mstatus.FS write in _start this
+ // line is an unhandled illegal instruction - and with a constant the compiler would fold it
+ // away and the check would pass on a board whose FPU is still off.
+ var seven: u32 = 7;
+ var fx: f32 = @floatFromInt(@as(*volatile u32, &seven).*);
+ fx = fx * 1.5 + 0.25;
+ soc.rom.print("MARK ZIG_P4_FPU 7*1.5+0.25=%u.%u expect=10.75\r\n", .{
+ @as(u32, @intFromFloat(fx)), @as(u32, @intFromFloat((fx - @trunc(fx)) * 100)),
+ });
+
+ const t0 = soc.cycles();
+ soc.rom.ets_delay_us(1000);
+ const per_ms = soc.cycles() - t0;
+ soc.rom.print("MARK ZIG_P4_CLOCK %u cycles per ms\r\n", .{@as(u32, @intCast(per_ms))});
+
+ var beat: u32 = 0;
+ while (true) : (beat += 1) {
+ soc.gpio.setHigh(led);
+ const high = soc.gpio.getLevel(led); // sampled while driven high, so it proves the toggle
+ soc.rom.ets_delay_us(500_000);
+ soc.gpio.setLow(led);
+ const low = soc.gpio.getLevel(led);
+ soc.rom.ets_delay_us(500_000);
+ if (beat % 4 == 0) {
+ soc.rom.print("MARK ZIG_P4_ALIVE beat=%u gpio%u high=%u low=%u\r\n", .{
+ beat, @as(u32, led), @as(u32, high), @as(u32, low),
+ });
+ }
+ }
+}
+
+fn fnv1a(comptime s: []const u8) u32 {
+ var h: u32 = 0x811c9dc5;
+ for (s) |b| {
+ h ^= b;
+ h = h *% 0x0100_0193;
+ }
+ return h;
+}
+
+/// Reset entry. The bootloader hands over with an unspecified stack pointer and with the FPU
+/// switched off, so: enable the F extension (mstatus.FS = Initial - the target includes `f`, and
+/// ESP-IDF only ever enables the unit lazily from a trap handler, which this image does not have),
+/// establish a stack in L2MEM, clear .bss, then call into Zig proper.
+export fn _start() linksection(".text.entry") callconv(.naked) noreturn {
+ asm volatile (
+ \\ li t0, 1 << 13
+ \\ csrs mstatus, t0
+ \\ la sp, __stack_top
+ \\ mv fp, sp
+ \\ la t0, __bss_start
+ \\ la t1, __bss_end
+ \\ bgeu t0, t1, 2f
+ \\1:
+ \\ sw zero, 0(t0)
+ \\ addi t0, t0, 4
+ \\ bltu t0, t1, 1b
+ \\2:
+ \\ j zig_main
+ );
+}
+
+/// The current spelling: std wraps a bare three-argument `panic` in a compatibility shim whose own
+/// comment calls it deprecated.
+pub const panic = std.debug.FullPanic(struct {
+ fn call(msg: []const u8, _: ?usize) noreturn {
+ soc.rom.print("MARK ZIG_P4_PANIC %s\r\n", .{msg.ptr});
+ while (true) {}
+ }
+}.call);