From f5f8068fac59b4f16046c2022c2fc7c7e447ef4c Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 25 Aug 2026 12:40:53 -0300 Subject: zig-p4: pure-Zig ESP32-P4 toolchain build.zig generates the linker script and drives Zig's own LLD; tools/image.zig turns the ELF into a flashable image and tools/{rom,serial}.zig speak the mask ROM loader over the UART. No CMake, ninja, idf.py, esptool, or external linker. src/soc.zig is a comptime register model over ESP-IDF's own *_reg.h headers; src/hal/ adds peripheral sequences; src/io/ implements std.Io for the chip; src/oracle/ diffs this HAL against ESP-IDF's on the die. --- tools/encode.sh | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100755 tools/encode.sh (limited to 'tools/encode.sh') diff --git a/tools/encode.sh b/tools/encode.sh new file mode 100755 index 0000000..bbb1483 --- /dev/null +++ b/tools/encode.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# Encoding oracle for the ESP32-P4's vendor ISA extensions (xespv / xesploop, "PIE"). +# +# Upstream LLVM - and therefore Zig's integrated assembler - cannot spell these mnemonics, so +# inline assembly does not close the gap: it goes through the same assembler and fails the same way. +# +# asm volatile ("esp.vld.128.ip q0, a0, 16") +# -> error: :1:2: unrecognized instruction mnemonic +# +# But the encodings are just words, and `.insn` places any word into the instruction stream. So +# assemble once, offline, with a toolchain that does know the mnemonics (Espressif's GAS), and bake +# the words into the source. The vendor toolchain becomes a build-time oracle, not a runtime +# dependency: nothing ships, `zig build` never invokes it, and each word sits next to the mnemonic +# it came from. Registers named by the encoding must then be pinned by hand ("{a0}" constraints), +# because LLVM cannot see through a `.insn`. +# +# Usage: tools/encode.sh 'esp.vld.128.ip q0, a0, 16' ['esp.movx.w.sar a1' ...] +# tools/encode.sh -f list.txt # one instruction per line, # comments ok +# +# Output: 0x0201223b .insn 4, 0x0201223b // esp.vld.128.ip q0, a0, 16 +# +# The -march string is load-bearing: the vendor extension is versioned and the versions do not +# share encodings. `esp.vld.128.ip q0, a0, 16` is 0x0311009f under `xespv` (defaults to 1.0) and +# 0x0201223b under `xespv2p1`. The P4 wants the latter - it is the pair in the .riscv.attributes of +# every ESP-IDF image for this chip (xesploop1p0_xespv2p1), and 0x0201223b is what Espressif's +# objdump reads back as this mnemonic out of such an image. + +set -e + +MARCH=rv32imafc_xespv2p1_xesploop1p0 + +AS=$(ls "$HOME"/.espressif/tools/riscv32-esp-elf/*/riscv32-esp-elf/bin/riscv32-esp-elf-as 2>/dev/null | head -1) +OD=$(ls "$HOME"/.espressif/tools/riscv32-esp-elf/*/riscv32-esp-elf/bin/riscv32-esp-elf-objdump 2>/dev/null | head -1) +[ -x "$AS" ] || { echo "no Espressif as found (needed only to generate encodings, never to build)" >&2; exit 1; } + +TMP=$(mktemp -d) +trap 'rm -rf "$TMP"' EXIT + +encode() { + printf '%s\n' "$1" > "$TMP/in.S" + if "$AS" -march="$MARCH" -o "$TMP/out.o" "$TMP/in.S" 2>"$TMP/err"; then + word=$("$OD" -d "$TMP/out.o" | awk '/^[[:space:]]*0:/ {print $2; exit}') + printf '0x%-10s .insn 4, 0x%s // %s\n' "$word" "$word" "$1" + else + printf '%-12s // %s -- %s\n' "REJECTED" "$1" "$(sed -n '2s/.*Error: //p' "$TMP/err")" + fi +} + +if [ "$1" = "-f" ]; then + sed 's/#.*//; s/^[[:space:]]*//; s/[[:space:]]*$//; /^$/d' "$2" | while IFS= read -r line; do + encode "$line" + done +else + for insn in "$@"; do encode "$insn"; done +fi -- cgit v1.3