summaryrefslogtreecommitdiff
path: root/src/main.zig
blob: 2e650604226772e3e9de9a466a87eec1cece37b3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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);