Files
tranZPUter/build.sh
Philip Smart d6fca362da build.sh: default to the buildable emuMZ FPGA revision + create TZFS tmp dir
The SW-700 v1.3 coreMZ FPGA now builds license-free (Quartus 17.1 Lite + the
TBB stub fix): coreMZ_E115_emuMZ compiles 0 errors -> a 3.5MB .sof. Switch the
default -C from SoftCPU (whose .vhd has WIP syntax errors) to emuMZ, and create
software/TZFS/tmp so the GLASS assembler can write its intermediates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:07:48 +01:00

166 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
#########################################################################################################
## tranZPUter SW - build script (CPLD + FPGA bitstreams + software: zOS, TZFS, ROMs, CPM)
## Copyright (c) 2019-2026 Philip Smart <philip.smart@net2net.org>. GPL v3. See license.txt.
##
## Produces, from the command line (no GUI), the loadable outputs for the tranZPUter SW boards:
## cpld - the CPLD programming files (.pof) - MAX7000AE/S, Quartus II 13.0.1
## fpga - the FPGA bitstreams (.sof, + .jic for the EPCS boot flash)
## SW-700 v1.2 = Cyclone III EP3C25 (Quartus 13.1); v1.3 & Fusion = Cyclone IV
## EP4CE115/EP4CE75 (Quartus 17.1). The right Quartus version is auto-selected per device.
## software - zOS (via the zSoft submodule) + TZFS + the SA-1510 monitor ROMs + CP/M (GLASS/Java)
## all - everything (default)
##
## The two logic tool chains run NATIVELY when a matching Quartus is found, otherwise inside their Docker
## images (built by setup_tranZPUter.sh). Java (for the GLASS Z80 assembler) is used natively.
##
## Usage: ./build.sh [-t cpld|fpga|software|all] [-V v1.2|v1.3] [-M MZ80A|MZ700|MZ2000|all]
## [-D E115|E75] [-C base|emuMZ|SoftCPU] [-m auto|native|docker]
## Defaults: -t all -V v1.3 -M all -D E115 -C emuMZ (SoftCPU + base revisions are WIP - see README). Outputs are collected under ./build/output/.
#########################################################################################################
set -uo pipefail
ROOTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUTDIR="${ROOTDIR}/build/output"
TARGET="all" ; FVER="v1.3" ; FMACH="all" ; FDEV="E115" ; FCFG="emuMZ"
METHOD="${TZPU_METHOD:-auto}"
# Quartus Docker images per device family (override via env). The setup script builds these.
IMG_CPLD="${TZPU_QUARTUS_CPLD_IMAGE:-tzpu-quartus:13.0.1}" # MAX7000 (EPM7xxx)
IMG_FPGA_C3="${TZPU_QUARTUS_C3_IMAGE:-tzpu-quartus:13.1}" # Cyclone III (EP3Cxx)
IMG_FPGA_C4="${TZPU_QUARTUS_C4_IMAGE:-tzpu-quartus:17.1}" # Cyclone IV (EP4CExx)
RED='\033[0;31m'; GRN='\033[0;32m'; YEL='\033[1;33m'; CYN='\033[0;36m'; NC='\033[0m'
info(){ echo -e "${CYN}[INFO]${NC} $*"; }
ok(){ echo -e "${GRN}[OK]${NC} $*"; }
warn(){ echo -e "${YEL}[WARN]${NC} $*" >&2; }
fail(){ echo -e "${RED}[FAIL]${NC} $*" >&2; exit 1; }
usage(){ grep -E '^## ' "$0" | sed 's/^## //'; exit "${1:-0}"; }
while getopts ":t:V:M:D:C:m:h" o; do case "$o" in
t) TARGET="$(echo "$OPTARG"|tr 'A-Z' 'a-z')";; V) FVER="$OPTARG";; M) FMACH="$OPTARG";;
D) FDEV="$OPTARG";; C) FCFG="$OPTARG";; m) METHOD="$(echo "$OPTARG"|tr 'A-Z' 'a-z')";; h) usage 0;; *) usage 1;;
esac; done
case "$TARGET" in cpld|fpga|software|all) ;; *) fail "bad -t '$TARGET'";; esac
mkdir -p "${OUTDIR}"
# ------------------------------------------------------------------------------------------------------
# Pick the Quartus version tag + Docker image for a device string.
# ------------------------------------------------------------------------------------------------------
quartus_for_device(){ # $1=DEVICE -> echoes "<ver> <image>"
case "$1" in
EPM7*) echo "13.0.1 ${IMG_CPLD}" ;;
EP3C*) echo "13.1 ${IMG_FPGA_C3}" ;;
EP4CE*) echo "17.1 ${IMG_FPGA_C4}" ;;
*) return 1 ;;
esac
}
# All candidate quartus bin dirs across the different image layouts (13.0sp1 vs 17.1 vs plain).
QBIN='/opt/altera/quartus/bin:/opt/altera/13.0sp1/quartus/bin:/opt/altera/13.1/quartus/bin:/opt/altera/17.1/quartus/bin'
have_image(){ command -v docker >/dev/null 2>&1 && docker image inspect "$1" >/dev/null 2>&1; }
native_quartus(){ command -v quartus_sh >/dev/null 2>&1; }
# ------------------------------------------------------------------------------------------------------
# Compile one Quartus revision. $1=build-dir (rel to ROOT) $2=revision(qsf basename) $3=DEVICE $4=jic?(EPCSxx|"")
# ------------------------------------------------------------------------------------------------------
compile_quartus(){
local bdir="$1" rev="$2" dev="$3" epcs="${4:-}"
local qinfo; qinfo="$(quartus_for_device "$dev")" || { warn "unknown device $dev in $bdir/$rev - skipping"; return 1; }
local ver image; read -r ver image <<<"$qinfo"
mkdir -p "${ROOTDIR}/build/logs"
local log="${ROOTDIR}/build/logs/${rev}_$(echo "$bdir"|tr '/' '_').log"
# The Quartus PROJECT is the .qpf basename; the .qsf is a REVISION of it. Compile as
# `quartus_sh --flow compile <project> -c <revision>` (as the Jenkins CI does), e.g. coreMZ has the
# revisions coreMZ_E115, coreMZ_E75_SoftCPU, ... A single-revision CPLD has project == revision.
local proj; proj="$(basename "$(ls "${ROOTDIR}/${bdir}"/*.qpf 2>/dev/null | head -1)" .qpf 2>/dev/null)"; [ -z "$proj" ] && proj="$rev"
# Delete any prior output first so the post-build check can never pass on a stale file.
local flow="rm -f output_files/${rev}.sof output_files/${rev}.pof output_files/${rev}.jic; quartus_sh --flow compile ${proj} -c ${rev}"
[ -n "$epcs" ] && flow="${flow} && (cd output_files && quartus_cpf -c -d ${epcs} ${rev}.sof ${rev}.jic || true)"
local m="$METHOD"
if [ "$m" = auto ]; then native_quartus && m=native || m=docker; fi
info "Compile ${bdir}/${rev} (${dev}, Quartus ${ver}, ${m})"
if [ "$m" = native ]; then
( cd "${ROOTDIR}/${bdir}" && bash -c "${flow}" ) >"$log" 2>&1
else
have_image "$image" || { warn "image ${image} missing (run setup_tranZPUter.sh) - skipping ${rev}"; return 1; }
docker run --rm -v "${ROOTDIR}":/work -w "/work/${bdir}" "$image" \
bash -c "export PATH=\$PATH:${QBIN} && ${flow}" >"$log" 2>&1
fi
grep -qiE 'Full Compilation was successful' "$log" || warn "compile did not report success (see ${log#${ROOTDIR}/})"
local pof="${ROOTDIR}/${bdir}/output_files/${rev}.pof" sof="${ROOTDIR}/${bdir}/output_files/${rev}.sof"
local tag; tag="$(echo "${bdir}" | sed -E 's#(CPLD|FPGA)/##; s#/build##; s#/#_#g')_${rev}"
if [ -f "$pof" ]; then cp "$pof" "${OUTDIR}/${tag}.pof"; ok "-> output/${tag}.pof"; return 0; fi
if [ -f "$sof" ]; then cp "$sof" "${OUTDIR}/${tag}.sof"; [ -f "${sof%.sof}.jic" ] && cp "${sof%.sof}.jic" "${OUTDIR}/${tag}.jic"; ok "-> output/${tag}.sof"; return 0; fi
warn "no .pof/.sof produced for ${bdir}/${rev}"; return 1
}
device_of(){ grep -iE '^\s*set_global_assignment -name DEVICE ' "$1" 2>/dev/null | grep -v '^#' | head -1 | grep -oE '(EPM[0-9A-Z-]+|EP[34]CE?[0-9A-Z]+)'; }
# ------------------------------------------------------------------------------------------------------
# Target: CPLD - build the standalone variants + the SW700 v1.3 per-machine CPLD.
# ------------------------------------------------------------------------------------------------------
build_cpld(){
info "=== CPLD bitstreams ==="
local qsf bdir rev dev
while IFS= read -r qsf; do
bdir="$(dirname "${qsf#${ROOTDIR}/}")"; rev="$(basename "$qsf" .qsf)"; dev="$(device_of "$qsf")"
[ -n "$dev" ] && compile_quartus "$bdir" "$rev" "$dev"
done < <(find "${ROOTDIR}/CPLD/SW/build" "${ROOTDIR}/CPLD/EPM7160" "${ROOTDIR}/CPLD/SW700/${FVER}" -name '*.qsf' 2>/dev/null \
| grep -vE 'greybox|/db/|bak|snap' | grep -iE 'tranZPUterSW\.qsf|_EPM7160\.qsf|tzpuSW700_' )
}
# ------------------------------------------------------------------------------------------------------
# Target: FPGA - SW700 <ver> per machine, the chosen device+config (v1.2 = the single VideoController700).
# ------------------------------------------------------------------------------------------------------
build_fpga(){
info "=== FPGA bitstreams (${FVER}, machines=${FMACH}, device=${FDEV}, config=${FCFG}) ==="
if [ "$FVER" = "v1.2" ]; then
local qsf="${ROOTDIR}/FPGA/SW700/v1.2/build/VideoController700.qsf"
[ -f "$qsf" ] && compile_quartus "FPGA/SW700/v1.2/build" "VideoController700" "$(device_of "$qsf")" "EPCS16"
return
fi
local machines="$FMACH"; [ "$FMACH" = all ] && machines="MZ80A MZ700 MZ2000"
local m rev qsf dev
for m in $machines; do
rev="coreMZ_${FDEV}"; [ "$FCFG" != base ] && rev="coreMZ_${FDEV}_${FCFG}"
qsf="${ROOTDIR}/FPGA/SW700/${FVER}/${m}/build/${rev}.qsf"
if [ -f "$qsf" ]; then dev="$(device_of "$qsf")"; compile_quartus "FPGA/SW700/${FVER}/${m}/build" "$rev" "$dev" "EPCS16"
else warn "no FPGA project ${qsf#${ROOTDIR}/}"; fi
done
}
# ------------------------------------------------------------------------------------------------------
# Target: software - zOS (zSoft submodule) + TZFS + monitor ROMs + CP/M (GLASS/Java).
# ------------------------------------------------------------------------------------------------------
build_software(){
info "=== software: zOS + TZFS + ROMs + CP/M ==="
command -v java >/dev/null 2>&1 || warn "java not found - TZFS/ROM/CPM assembly (GLASS) will fail."
# zOS via the zSoft submodule (method-aware build.sh). EVO (SharpMZ) + K64F (tranZPUter) images.
if [ -x "${ROOTDIR}/software/zSoft/build.sh" ]; then
( cd "${ROOTDIR}/software/zSoft" && ./build.sh -C EVO -O zos -o 0 -M 0x1FD80 -B 0x0000 -S 0x3D80 -N 0x4000 -A 0x100000 -a 0x80000 -n 0x0000 -s 0x0000 -d -Z ) \
&& ok "zOS (EVO/SharpMZ) built" || warn "zOS EVO build reported errors."
else warn "software/zSoft submodule not checked out (git submodule update --init --recursive)."; fi
# TZFS + SA-1510 monitor ROMs + CP/M via the bundled GLASS assembler.
local t="${ROOTDIR}/software/TZFS/tools"
if [ -d "$t" ]; then
mkdir -p "${ROOTDIR}/software/TZFS/tmp" # the assemble scripts write intermediate .obj here
( cd "$t" && ./assemble_tzfs.sh && ./assemble_roms.sh && ./assemble_cpm.sh ) >/dev/null 2>&1 \
&& ok "TZFS + monitor ROMs + CP/M assembled" || warn "TZFS/ROM/CPM assembly reported errors."
cp "${ROOTDIR}"/software/TZFS/roms/*.rom "${OUTDIR}/" 2>/dev/null || true
cp "${ROOTDIR}"/software/TZFS/roms/*.mzf "${OUTDIR}/" 2>/dev/null || true
else warn "software/TZFS submodule not checked out."; fi
}
echo "============================================================================"
echo " tranZPUter SW build (target=${TARGET}, method=${METHOD}) -> ${OUTDIR#${ROOTDIR}/}"
echo "============================================================================"
case "$TARGET" in
cpld) build_cpld ;;
fpga) build_fpga ;;
software) build_software ;;
all) build_cpld; build_fpga; build_software ;;
esac
echo; ok "Build finished. Loadable outputs are in ${OUTDIR#${ROOTDIR}/} :"
ls -1 "${OUTDIR}" 2>/dev/null | sed 's/^/ /' | head -40