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>
166 lines
10 KiB
Bash
Executable File
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
|