SHELL := /bin/bash
.SHELLFLAGS := -euo pipefail -c
.ONESHELL:

FPGA_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
REPO_DIR := $(abspath $(FPGA_DIR)/../../..)
FPGA_SCRIPT_DIR := $(FPGA_DIR)/flow/vivado/scripts
TOOL_SCRIPT_DIR := $(FPGA_DIR)/tools
SOC_DIR := $(REPO_DIR)/soc
SOC_SW_DIR := $(SOC_DIR)/software
SOC_OUT_DIR := $(SOC_DIR)/out
HW_DIR := $(REPO_DIR)/hardware
HW_GENERATED_DIR := $(HW_DIR)/generated
GPS_SYSTEM_DIR ?= $(HW_DIR)/external/gps/rtl
OUT_DIR := $(FPGA_DIR)/out
LOG_DIR := $(OUT_DIR)/logs

include $(REPO_DIR)/tools/project.mk

VIVADO ?= /tools/Xilinx/2025.2/Vivado/bin/vivado
OPENOCD ?= openocd
GDB ?= $(CROSS)gdb
GDB_HOST ?= localhost
GDB_PORT ?= 3333
OPENOCD_ADAPTER_SPEED ?= 1000
UART_PORT ?= /dev/ttyUSB2
UART_BAUD ?= 460800
BOARD ?= vcu118
PART_NAME ?= xcvu9p-flga2104-2L-e
BOARD_PART ?= xilinx.com:vcu118:part0:2.4
SOC_CLK_HZ ?= 100000000
GPS_ACQ_CLK_HZ ?= 100000000
GPS_TRCK_CLK_HZ ?= 125000000
GPS_RF_CLK_HZ ?= 16367981
# GPS RF source for the generated FPGA top.
#   pmod      : external 4-bit RF data + RF clock from the 3.3 V PMOD header
#   generated : internal RF clock and zero RF data
#   ethernet  : old VCU118 raw-Ethernet RF streamer path
RF_INPUT_MODE ?= pmod
BSP_UART_CLOCK_HZ ?= $(SOC_CLK_HZ)
BSP_UART_BAUDRATE ?= $(UART_BAUD)
BSP_CLINT_TIMEBASE_HZ ?= 1000000
# DTIM is Rocket's scratchpad RAM; hardware generator and software must agree.
DTIM_BASE ?= 0x80000000
DTIM_SIZE ?= 0x00100000
MAKE_BIN ?= make
ETH_RF_IFACE ?= enx14ebb6850432
ETH_RF_INPUT ?= $(OUT_DIR)/rf/gps_rf_input.bin
ETH_RF_SAMPLE_FORMAT ?= real4-signmag
ifneq ($(strip $(SAMPLE_FORMAT)),)
ETH_RF_SAMPLE_FORMAT := $(SAMPLE_FORMAT)
endif
ETH_RF_CHUNK_SAMPLES ?= 17800
ETH_RF_MTU ?= 9000
ETH_RF_PRELOAD_SAMPLES ?= 131072
ETH_RF_MAX_SAMPLES ?= 0
ETH_RF_MAX_BYTES ?= 0
ETH_RF_SENDER ?= $(OUT_DIR)/tools/send_gps_rf

BITSTREAM_OFFSET ?= 0x00000000
APP_FLASH_OFFSET ?= 0x06000000
FLASH_SIZE_MB ?= 128
FLASH_INTERFACE ?= SPIx4
CFGMEM_PART ?= mt25qu01g-spi-x1_x2_x4
FLASH_VERIFY ?= 0
FLASH_ERASE ?= 1
FLASH_REGIONS ?= auto
FLASH_FORCE ?= 0
GPS_DEBUG ?= 1
GPS_DEBUG_LEVEL ?= 4
GPS_ESKF_ENABLE ?= 1
GPS_EPHEMERIS_FLASH_ENABLE ?= 1
GPS_EPHEMERIS_FLASH_SEED_INITIAL ?= 1
GPS_EPHEMERIS_FLASH_BASE ?= 0x07fe0000
GPS_NAV_CONTEXT_ENABLE ?= 1
GPS_NAV_CONTEXT_FLASH_BASE ?= 0x07fc0000
BSP_SPI_FLASH_PROFILE ?= 2
IMU_SENSOR ?= auto

FLASH_MCS ?= $(OUT_DIR)/flash/vcu118_rocket.mcs
FLASH_BIN ?= $(OUT_DIR)/flash/vcu118_rocket.bin
BIT_FILE ?= $(OUT_DIR)/bitstream/vcu118_rocket.bit
DEBUG_DIR := $(FPGA_DIR)/flow/debug
OPENOCD_CFG ?= $(DEBUG_DIR)/openocd_vcu118_bscan.cfg
GDB_LOAD_SCRIPT ?= $(DEBUG_DIR)/gdb_load_and_run.gdb
GDB_ATTACH_SCRIPT ?= $(DEBUG_DIR)/gdb_attach.gdb
app ?=
ifneq ($(strip $(app)),)
APP ?= $(app)
else
APP ?= hello_uart
endif

FLASH_REGION_DIR ?= $(OUT_DIR)/flash/regions
FLASH_STATE ?= $(OUT_DIR)/flash/programmed_regions.tsv
BITSTREAM_MCS ?= $(FLASH_REGION_DIR)/bitstream.mcs
APP_MCS ?= $(FLASH_REGION_DIR)/app.mcs
APP_BUILD_DIR ?= $(SOC_OUT_DIR)/software/$(APP)
BOOTLOADER_BUILD_DIR ?= $(SOC_OUT_DIR)/software/bootloader
APP_ELF ?= $(APP_BUILD_DIR)/$(APP).elf
APP_FLASH_IMAGE ?= $(SOC_OUT_DIR)/flash/$(APP).flash.bin
FIRRTL_DIR ?= $(HW_GENERATED_DIR)/vcu118-soc
VERILOG_DIR ?= $(HW_GENERATED_DIR)/vcu118-soc-verilog

SOFTWARE_TARGETS := software
BUILD_TARGETS := chisel rtl soc bitstream fpga-config
FLASH_IMAGE_TARGETS := flash-image flash-regions
PROGRAM_TARGETS := \
	program-fpga \
	program-flash \
	program-flash-elf \
	program-flash-bitstream \
	program-flash-regions \
	program-flash-all \
	program-flash-combined
DEBUG_TARGETS := debug-openocd debug-load debug-gdb uart-listen eth-rf-host eth-rf-caps eth-rf-send eth-rf-stop eth-rf-send-python
ALIASES := program-app program-bitstream
CLEAN_TARGETS := clean-sw clean-fpga clean-flash prune clean

.PHONY: help all \
	$(SOFTWARE_TARGETS) \
	$(BUILD_TARGETS) \
	$(FLASH_IMAGE_TARGETS) \
	$(PROGRAM_TARGETS) \
	$(DEBUG_TARGETS) \
	$(ALIASES) \
	$(CLEAN_TARGETS) \
	FORCE

help:
	@printf '%s\n' \
	  'VCU118 FPGA targets:' \
	  '  make software       Build bootloader, hello app, and SPI flash app image' \
	  '  make chisel         Build Scala/Chisel generator and emit FIRRTL' \
	  '  make rtl            Convert FIRRTL to split SystemVerilog and wrapper' \
	  '  make bitstream      Run Vivado non-project synth/place/route/bitgen' \
	  '  make flash-image    Generate one combined MCS/BIN image' \
	  '  make flash-regions  Generate sparse per-region MCS images' \
	  '  make program-fpga   Program FPGA SRAM with BIT_FILE' \
	  '  make program-flash  Program changed app flash region only' \
	  '  make program-flash-bitstream' \
	  '                      Program changed bitstream flash region only' \
	  '  make program-flash-all' \
	  '                      Program changed bitstream and app regions separately' \
	  '  make program-flash-combined' \
	  '                      Legacy one-MCS programming path' \
	  '  make debug-openocd Open OpenOCD BSCAN/GDB server for programmed FPGA' \
	  '  make debug-load    Load APP ELF over GDB and run from DTIM' \
	  '  make debug-gdb     Attach interactive GDB to OpenOCD' \
	  '  make uart-listen   Listen to board UART' \
	  '  make eth-rf-caps   Allow non-sudo Ethernet RF sender execution via file capabilities' \
	  '  make eth-rf-send   Stream GPS RF binary to FPGA over raw Ethernet using C host app' \
	  '                      Requires RF_INPUT_MODE=ethernet bitstream' \
	  '  make eth-rf-stop   Stop FPGA Ethernet RF playback' \
	  '  make eth-rf-send-python' \
	  '                      Stream GPS RF binary using Python fallback' \
	  '  make fpga-config   Show board clocks, RF, flash, UART, and Vivado config' \
	  '' \
	  'Config env:' \
	  '  BOARD=$(BOARD)' \
	  '  PART_NAME=$(PART_NAME)' \
	  '  BOARD_PART=$(BOARD_PART)' \
	  '  SOC_CLK_HZ=$(SOC_CLK_HZ)' \
	  '  GPS_ACQ_CLK_HZ=$(GPS_ACQ_CLK_HZ)' \
	  '  GPS_TRCK_CLK_HZ=$(GPS_TRCK_CLK_HZ)' \
	  '  GPS_RF_CLK_HZ=$(GPS_RF_CLK_HZ)' \
	  '  RF_INPUT_MODE=$(RF_INPUT_MODE)  # pmod, generated, or ethernet' \
	  '  GPS_SYSTEM_DIR=$(GPS_SYSTEM_DIR)' \
	  '  BSP_UART_CLOCK_HZ=$(BSP_UART_CLOCK_HZ)' \
	  '  BSP_UART_BAUDRATE=$(BSP_UART_BAUDRATE)' \
	  '  BSP_CLINT_TIMEBASE_HZ=$(BSP_CLINT_TIMEBASE_HZ)' \
	  '  DTIM_BASE=$(DTIM_BASE)' \
	  '  DTIM_SIZE=$(DTIM_SIZE)' \
	  '  BITSTREAM_OFFSET=$(BITSTREAM_OFFSET)' \
	  '  APP_FLASH_OFFSET=$(APP_FLASH_OFFSET)' \
	  '  FLASH_SIZE_MB=$(FLASH_SIZE_MB)' \
	  '  CFGMEM_PART=$(CFGMEM_PART)' \
	  '  FLASH_REGIONS=$(FLASH_REGIONS)  # auto, all, app, bitstream, or comma list' \
	  '  FLASH_FORCE=$(FLASH_FORCE)      # 1 forces unchanged regions too' \
	  '  APP=$(APP)' \
	  '  APP_ELF=$(APP_ELF)' \
	  '  GPS_ESKF_ENABLE=$(GPS_ESKF_ENABLE)' \
	  '  IMU_SENSOR=$(IMU_SENSOR)' \
	  '  UART_PORT=$(UART_PORT)' \
	  '  ETH_RF_IFACE=$(ETH_RF_IFACE)' \
	  '  ETH_RF_INPUT=$(ETH_RF_INPUT)' \
	  '  ETH_RF_CHUNK_SAMPLES=$(ETH_RF_CHUNK_SAMPLES)' \
	  '  ETH_RF_MTU=$(ETH_RF_MTU)' \
	  '  ETH_RF_PRELOAD_SAMPLES=$(ETH_RF_PRELOAD_SAMPLES)' \
	  '  ETH_RF_MAX_SAMPLES=$(ETH_RF_MAX_SAMPLES)' \
	  '  GDB_PORT=$(GDB_PORT)' \
	  '  OPENOCD_ADAPTER_SPEED=$(OPENOCD_ADAPTER_SPEED)'

all: bitstream

fpga-config:
	@printf '%s\n' \
	  'FPGA config:' \
	  '  BOARD=$(BOARD)' \
	  '  PART_NAME=$(PART_NAME)' \
	  '  BOARD_PART=$(BOARD_PART)' \
	  '  SOC_CLK_HZ=$(SOC_CLK_HZ)' \
	  '  GPS_ACQ_CLK_HZ=$(GPS_ACQ_CLK_HZ)' \
	  '  GPS_TRCK_CLK_HZ=$(GPS_TRCK_CLK_HZ)' \
	  '  GPS_RF_CLK_HZ=$(GPS_RF_CLK_HZ)' \
	  '  RF_INPUT_MODE=$(RF_INPUT_MODE)' \
	  '  GPS_SYSTEM_DIR=$(GPS_SYSTEM_DIR)' \
	  '  BSP_UART_CLOCK_HZ=$(BSP_UART_CLOCK_HZ)' \
	  '  BSP_UART_BAUDRATE=$(BSP_UART_BAUDRATE)' \
	  '  BSP_CLINT_TIMEBASE_HZ=$(BSP_CLINT_TIMEBASE_HZ)' \
	  '  DTIM_BASE=$(DTIM_BASE)' \
	  '  DTIM_SIZE=$(DTIM_SIZE)' \
	  '  BIT_FILE=$(BIT_FILE)' \
	  '  BITSTREAM_OFFSET=$(BITSTREAM_OFFSET)' \
	  '  APP_FLASH_OFFSET=$(APP_FLASH_OFFSET)' \
	  '  FLASH_SIZE_MB=$(FLASH_SIZE_MB)' \
	  '  FLASH_INTERFACE=$(FLASH_INTERFACE)' \
	  '  CFGMEM_PART=$(CFGMEM_PART)' \
	  '  UART_PORT=$(UART_PORT)' \
	  '  UART_BAUD=$(UART_BAUD)' \
	  '  GPS_ESKF_ENABLE=$(GPS_ESKF_ENABLE)' \
	  '  GPS_EPHEMERIS_FLASH_ENABLE=$(GPS_EPHEMERIS_FLASH_ENABLE)' \
	  '  GPS_EPHEMERIS_FLASH_SEED_INITIAL=$(GPS_EPHEMERIS_FLASH_SEED_INITIAL)' \
	  '  GPS_EPHEMERIS_FLASH_BASE=$(GPS_EPHEMERIS_FLASH_BASE)' \
	  '  GPS_NAV_CONTEXT_ENABLE=$(GPS_NAV_CONTEXT_ENABLE)' \
	  '  GPS_NAV_CONTEXT_FLASH_BASE=$(GPS_NAV_CONTEXT_FLASH_BASE)' \
	  '  BSP_SPI_FLASH_PROFILE=$(BSP_SPI_FLASH_PROFILE)' \
	  '  IMU_SENSOR=$(IMU_SENSOR)'

software:
	@$(MAKE_BIN) -C "$(SOC_SW_DIR)" TOOLCHAIN="$(TOOLCHAIN)" APP="$(APP)" APP_FLASH_OFFSET="$(APP_FLASH_OFFSET)" BSP_UART_CLOCK_HZ="$(BSP_UART_CLOCK_HZ)" BSP_UART_BAUDRATE="$(BSP_UART_BAUDRATE)" BSP_CLINT_TIMEBASE_HZ="$(BSP_CLINT_TIMEBASE_HZ)" BSP_SPI_FLASH_PROFILE="$(BSP_SPI_FLASH_PROFILE)" BSP_DTIM_BASE="$(DTIM_BASE)" BSP_DTIM_SIZE="$(DTIM_SIZE)" GPS_DEBUG="$(GPS_DEBUG)" GPS_DEBUG_LEVEL="$(GPS_DEBUG_LEVEL)" GPS_ESKF_ENABLE="$(GPS_ESKF_ENABLE)" GPS_EPHEMERIS_FLASH_ENABLE="$(GPS_EPHEMERIS_FLASH_ENABLE)" GPS_EPHEMERIS_FLASH_SEED_INITIAL="$(GPS_EPHEMERIS_FLASH_SEED_INITIAL)" GPS_EPHEMERIS_FLASH_BASE="$(GPS_EPHEMERIS_FLASH_BASE)" GPS_NAV_CONTEXT_ENABLE="$(GPS_NAV_CONTEXT_ENABLE)" GPS_NAV_CONTEXT_FLASH_BASE="$(GPS_NAV_CONTEXT_FLASH_BASE)" IMU_SENSOR="$(IMU_SENSOR)" bootloader flash-image

$(APP_ELF): FORCE
	@$(MAKE_BIN) -C "$(SOC_SW_DIR)" TOOLCHAIN="$(TOOLCHAIN)" APP="$(APP)" APP_FLASH_OFFSET="$(APP_FLASH_OFFSET)" BSP_UART_CLOCK_HZ="$(BSP_UART_CLOCK_HZ)" BSP_UART_BAUDRATE="$(BSP_UART_BAUDRATE)" BSP_CLINT_TIMEBASE_HZ="$(BSP_CLINT_TIMEBASE_HZ)" BSP_SPI_FLASH_PROFILE="$(BSP_SPI_FLASH_PROFILE)" BSP_DTIM_BASE="$(DTIM_BASE)" BSP_DTIM_SIZE="$(DTIM_SIZE)" GPS_DEBUG="$(GPS_DEBUG)" GPS_DEBUG_LEVEL="$(GPS_DEBUG_LEVEL)" GPS_ESKF_ENABLE="$(GPS_ESKF_ENABLE)" GPS_EPHEMERIS_FLASH_ENABLE="$(GPS_EPHEMERIS_FLASH_ENABLE)" GPS_EPHEMERIS_FLASH_SEED_INITIAL="$(GPS_EPHEMERIS_FLASH_SEED_INITIAL)" GPS_EPHEMERIS_FLASH_BASE="$(GPS_EPHEMERIS_FLASH_BASE)" GPS_NAV_CONTEXT_ENABLE="$(GPS_NAV_CONTEXT_ENABLE)" GPS_NAV_CONTEXT_FLASH_BASE="$(GPS_NAV_CONTEXT_FLASH_BASE)" IMU_SENSOR="$(IMU_SENSOR)" app

chisel: software
	@cd "$(REPO_DIR)"
	"$(MILL)" -i 'rocketchip[6.7.0].assembly'
	rm -rf "$(FIRRTL_DIR)"
	mkdir -p "$(FIRRTL_DIR)"
	"$(JAVA_HOME)/bin/java" \
	  -DROCKET_DTIM_BASE="$(DTIM_BASE)" \
	  -DROCKET_DTIM_SIZE="$(DTIM_SIZE)" \
	  -DROCKET_SOC_CLOCK_HZ="$(SOC_CLK_HZ)" \
	  -jar out/rocketchip/6.7.0/assembly.dest/out.jar \
	  --dir "$(FIRRTL_DIR)" \
	  --top freechips.rocketchip.fpga.VCU118RocketSystem \
	  --config freechips.rocketchip.fpga.VCU118RocketConfig

rtl: chisel
	@cd "$(REPO_DIR)"
	rm -rf "$(VERILOG_DIR)"
	mkdir -p "$(VERILOG_DIR)" "$(OUT_DIR)/build/generated"
	$(FIRTOOL_ENV) "$(FIRTOOL)" \
	  "$(FIRRTL_DIR)/VCU118RocketSystem.fir" \
	  --split-verilog -o "$(VERILOG_DIR)" \
	  --preserve-aggregate=none \
	  --disable-annotation-unknown \
	  --disable-all-randomization \
	  --strip-debug-info
	find "$(VERILOG_DIR)" -maxdepth 1 -type f \( -name '*.sv' -o -name '*.v' \) \
	  -printf './%f\n' | sort > "$(VERILOG_DIR)/filelist.f"
	perl -0pi -e 's/^([ \t]*)if \([^\n]*\)\n[ \t]*assert__[^\n]*:\s*assert\([^\n]*\);\n//mg; s/^[ \t]*assert__[^\n]*:\s*assert\([^\n]*\);\n//mg' "$(VERILOG_DIR)"/*.sv
	python3 "$(TOOL_SCRIPT_DIR)/gen_vcu118_wrapper.py" \
	  --rocket-top "$(VERILOG_DIR)/VCU118RocketSystem.sv" \
	  --out "$(OUT_DIR)/build/generated/vcu118_rocket_top.sv" \
	  --rf-input-mode "$(RF_INPUT_MODE)"

soc: rtl

bitstream: rtl
	@mkdir -p "$(LOG_DIR)"
	cd "$(LOG_DIR)"
	GPS_SYSTEM_DIR="$(GPS_SYSTEM_DIR)" \
	RF_INPUT_MODE="$(RF_INPUT_MODE)" "$(VIVADO)" -mode batch \
	  -log "$(LOG_DIR)/build_bitstream.log" \
	  -journal "$(LOG_DIR)/build_bitstream.jou" \
	  -source "$(FPGA_SCRIPT_DIR)/build.tcl"

flash-image: software
	@if [[ ! -f "$(BIT_FILE)" ]]; then
	  echo "Missing bitstream: $(BIT_FILE)" >&2
	  echo "Run: make -C $(FPGA_DIR) bitstream" >&2
	  exit 1
	fi
	mkdir -p "$(LOG_DIR)"
	cd "$(LOG_DIR)"
	BIT_FILE="$(BIT_FILE)" \
	APP_FLASH_IMAGE="$(APP_FLASH_IMAGE)" \
	FLASH_MCS="$(FLASH_MCS)" \
	FLASH_BIN="$(FLASH_BIN)" \
	FLASH_SIZE_MB="$(FLASH_SIZE_MB)" \
	FLASH_INTERFACE="$(FLASH_INTERFACE)" \
	BITSTREAM_OFFSET="$(BITSTREAM_OFFSET)" \
	APP_FLASH_OFFSET="$(APP_FLASH_OFFSET)" \
	  "$(VIVADO)" -mode batch \
	    -log "$(LOG_DIR)/write_flash.log" \
	    -journal "$(LOG_DIR)/write_flash.jou" \
	    -source "$(FPGA_SCRIPT_DIR)/write_flash.tcl"

flash-regions:
	@selected="$(FLASH_REGIONS)"
	selected="$${selected,,}"
	selected="$${selected//,/ }"
	if [[ "$$selected" == "auto" ]]; then
	  selected="bitstream app"
	fi
	need_app=0
	need_bitstream=0
	region_names=()
	for region in $$selected; do
	  case "$$region" in
	    all)
	      need_app=1
	      need_bitstream=1
	      region_names+=(bitstream app)
	      ;;
	    app|application|software|hello)
	      need_app=1
	      region_names+=(app)
	      ;;
	    bit|bitstream)
	      need_bitstream=1
	      region_names+=(bitstream)
	      ;;
	    none|"")
	      ;;
	    *)
	      echo "Unknown FLASH_REGIONS entry: $$region" >&2
	      exit 1
	      ;;
	  esac
	done
	if [[ "$$need_app" == "1" ]]; then
	  $(MAKE_BIN) -C "$(FPGA_DIR)" software
	fi
	if [[ "$$need_bitstream" == "1" && ! -f "$(BIT_FILE)" ]]; then
	  echo "Missing bitstream: $(BIT_FILE)" >&2
	  echo "Run: make -C $(FPGA_DIR) bitstream" >&2
	  exit 1
	fi
	if [[ "$${#region_names[@]}" -eq 0 ]]; then
	  echo "No flash regions selected."
	  exit 0
	fi
	joined="$$(IFS=';'; echo "$${region_names[*]}")"
	mkdir -p "$(LOG_DIR)"
	cd "$(LOG_DIR)"
	BIT_FILE="$(BIT_FILE)" \
	APP_FLASH_IMAGE="$(APP_FLASH_IMAGE)" \
	BITSTREAM_MCS="$(BITSTREAM_MCS)" \
	APP_MCS="$(APP_MCS)" \
	FLASH_SIZE_MB="$(FLASH_SIZE_MB)" \
	FLASH_INTERFACE="$(FLASH_INTERFACE)" \
	BITSTREAM_OFFSET="$(BITSTREAM_OFFSET)" \
	APP_FLASH_OFFSET="$(APP_FLASH_OFFSET)" \
	FLASH_REGION_NAMES="$$joined" \
	  "$(VIVADO)" -mode batch \
	    -log "$(LOG_DIR)/write_flash_regions.log" \
	    -journal "$(LOG_DIR)/write_flash_regions.jou" \
	    -source "$(FPGA_SCRIPT_DIR)/write_flash_regions.tcl"

program-fpga:
	@mkdir -p "$(LOG_DIR)"
	cd "$(LOG_DIR)"
	BIT_FILE="$(BIT_FILE)" \
	  "$(VIVADO)" -mode batch \
	    -log "$(LOG_DIR)/program_fpga.log" \
	    -journal "$(LOG_DIR)/program_fpga.jou" \
	    -source "$(FPGA_SCRIPT_DIR)/program_fpga.tcl"

program-flash program-flash-elf program-app:
	@$(MAKE_BIN) -C "$(FPGA_DIR)" program-flash-regions FLASH_REGIONS=app

program-flash-bitstream program-bitstream:
	@$(MAKE_BIN) -C "$(FPGA_DIR)" program-flash-regions FLASH_REGIONS=bitstream

program-flash-all:
	@$(MAKE_BIN) -C "$(FPGA_DIR)" program-flash-regions FLASH_REGIONS=all

program-flash-regions:
	@selected="$(FLASH_REGIONS)"
	selected="$${selected,,}"
	selected="$${selected//,/ }"
	if [[ "$$selected" == "auto" ]]; then
	  selected="bitstream app"
	fi
	need_app=0
	need_bitstream=0
	for region in $$selected; do
	  case "$$region" in
	    all)
	      need_app=1
	      need_bitstream=1
	      ;;
	    app|application|software|hello)
	      need_app=1
	      ;;
	    bit|bitstream)
	      need_bitstream=1
	      ;;
	    none|"")
	      ;;
	    *)
	      echo "Unknown FLASH_REGIONS entry: $$region" >&2
	      exit 1
	      ;;
	  esac
	done
	if [[ "$$need_app" == "1" ]]; then
	  $(MAKE_BIN) -C "$(FPGA_DIR)" software
	fi
	if [[ "$$need_bitstream" == "1" && ! -f "$(BIT_FILE)" ]]; then
	  echo "Missing bitstream: $(BIT_FILE)" >&2
	  echo "Run: make -C $(FPGA_DIR) bitstream" >&2
	  exit 1
	fi
	state_line() {
	  local name="$$1"
	  local offset="$$2"
	  local source_file="$$3"
	  local mcs_file="$$4"
	  local size hash
	  size="$$(stat -c '%s' "$$source_file")"
	  hash="$$(sha256sum "$$source_file" | awk '{print $$1}')"
	  printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$$name" "$$offset" "$$size" "$$hash" "$$source_file" "$$mcs_file"
	}
	plan_names=()
	plan_mcs=()
	plan_state=()
	region_planned() {
	  local name="$$1"
	  local planned
	  for planned in "$${plan_names[@]}"; do
	    [[ "$$planned" == "$$name" ]] && return 0
	  done
	  return 1
	}
	add_region() {
	  local name="$$1"
	  local offset="$$2"
	  local source_file="$$3"
	  local mcs_file="$$4"
	  local line
	  region_planned "$$name" && return
	  if [[ ! -f "$$source_file" ]]; then
	    echo "Missing flash source for $$name: $$source_file" >&2
	    exit 1
	  fi
	  line="$$(state_line "$$name" "$$offset" "$$source_file" "$$mcs_file")"
	  if [[ "$(FLASH_FORCE)" != "1" && -f "$(FLASH_STATE)" ]] && grep -Fqx "$$line" "$(FLASH_STATE)"; then
	    echo "Skipping unchanged flash region: $$name"
	    return
	  fi
	  plan_names+=("$$name")
	  plan_mcs+=("$$mcs_file")
	  plan_state+=("$$line")
	}
	for region in $$selected; do
	  case "$$region" in
	    all)
	      add_region bitstream "$(BITSTREAM_OFFSET)" "$(BIT_FILE)" "$(BITSTREAM_MCS)"
	      add_region app "$(APP_FLASH_OFFSET)" "$(APP_FLASH_IMAGE)" "$(APP_MCS)"
	      ;;
	    app|application|software|hello)
	      add_region app "$(APP_FLASH_OFFSET)" "$(APP_FLASH_IMAGE)" "$(APP_MCS)"
	      ;;
	    bit|bitstream)
	      add_region bitstream "$(BITSTREAM_OFFSET)" "$(BIT_FILE)" "$(BITSTREAM_MCS)"
	      ;;
	    none|"")
	      ;;
	  esac
	done
	if [[ "$${#plan_names[@]}" -eq 0 ]]; then
	  echo "No changed flash regions to program."
	  exit 0
	fi
	region_names="$$(IFS=';'; echo "$${plan_names[*]}")"
	region_files="$$(IFS=';'; echo "$${plan_mcs[*]}")"
	mkdir -p "$(LOG_DIR)"
	cd "$(LOG_DIR)"
	BIT_FILE="$(BIT_FILE)" \
	APP_FLASH_IMAGE="$(APP_FLASH_IMAGE)" \
	BITSTREAM_MCS="$(BITSTREAM_MCS)" \
	APP_MCS="$(APP_MCS)" \
	FLASH_SIZE_MB="$(FLASH_SIZE_MB)" \
	FLASH_INTERFACE="$(FLASH_INTERFACE)" \
	BITSTREAM_OFFSET="$(BITSTREAM_OFFSET)" \
	APP_FLASH_OFFSET="$(APP_FLASH_OFFSET)" \
	FLASH_REGION_NAMES="$$region_names" \
	  "$(VIVADO)" -mode batch \
	    -log "$(LOG_DIR)/write_flash_regions.log" \
	    -journal "$(LOG_DIR)/write_flash_regions.jou" \
	    -source "$(FPGA_SCRIPT_DIR)/write_flash_regions.tcl"
	FLASH_REGION_NAMES="$$region_names" \
	FLASH_REGION_FILES="$$region_files" \
	FLASH_MCS="$(FLASH_MCS)" \
	CFGMEM_PART="$(CFGMEM_PART)" \
	FLASH_VERIFY="$(FLASH_VERIFY)" \
	FLASH_ERASE="$(FLASH_ERASE)" \
	  "$(VIVADO)" -mode batch \
	    -log "$(LOG_DIR)/program_flash.log" \
	    -journal "$(LOG_DIR)/program_flash.jou" \
	    -source "$(FPGA_SCRIPT_DIR)/program_flash.tcl"
	grep -q "Programmed flash" "$(LOG_DIR)/program_flash.log"
	mkdir -p "$$(dirname "$(FLASH_STATE)")"
	tmp="$(FLASH_STATE).tmp.$$$$"
	if [[ -f "$(FLASH_STATE)" ]]; then
	  cp "$(FLASH_STATE)" "$$tmp"
	else
	  : > "$$tmp"
	fi
	for line in "$${plan_state[@]}"; do
	  name="$${line%%$$'\t'*}"
	  next="$$tmp.next"
	  awk -F '\t' -v name="$$name" '$$1 != name' "$$tmp" > "$$next"
	  mv "$$next" "$$tmp"
	  printf '%s\n' "$$line" >> "$$tmp"
	done
	mv "$$tmp" "$(FLASH_STATE)"
	echo "Updated flash program state: $(FLASH_STATE)"

program-flash-combined: flash-image
	@mkdir -p "$(LOG_DIR)"
	cd "$(LOG_DIR)"
	FLASH_REGION_NAMES="combined" \
	FLASH_REGION_FILES="$(FLASH_MCS)" \
	FLASH_MCS="$(FLASH_MCS)" \
	CFGMEM_PART="$(CFGMEM_PART)" \
	FLASH_VERIFY="$(FLASH_VERIFY)" \
	FLASH_ERASE="$(FLASH_ERASE)" \
	  "$(VIVADO)" -mode batch \
	    -log "$(LOG_DIR)/program_flash_combined.log" \
	    -journal "$(LOG_DIR)/program_flash_combined.jou" \
	    -source "$(FPGA_SCRIPT_DIR)/program_flash.tcl"

debug-openocd:
	@"$(OPENOCD)" \
	  -c "set ADAPTER_SPEED $(OPENOCD_ADAPTER_SPEED)" \
	  -c "gdb_port $(GDB_PORT)" \
	  -f "$(OPENOCD_CFG)"

debug-load: $(APP_ELF)
	@"$(GDB)" -q "$(APP_ELF)" \
	  -ex "set confirm off" \
	  -ex "set pagination off" \
	  -ex "target extended-remote $(GDB_HOST):$(GDB_PORT)" \
	  -x "$(GDB_LOAD_SCRIPT)"

debug-gdb: $(APP_ELF)
	@"$(GDB)" -q "$(APP_ELF)" \
	  -ex "set confirm off" \
	  -ex "set pagination off" \
	  -ex "target extended-remote $(GDB_HOST):$(GDB_PORT)" \
	  -x "$(GDB_ATTACH_SCRIPT)"

uart-listen:
	@stty -F "$(UART_PORT)" "$(UART_BAUD)" cs8 -cstopb -parenb -ixon -ixoff raw
	@cat "$(UART_PORT)"

eth-rf-host: $(ETH_RF_SENDER)

$(ETH_RF_SENDER): $(FPGA_DIR)/flow/ethernet_rf/send_gps_rf.c
	@mkdir -p "$$(dirname "$@")"
	gcc -O3 -std=c11 -Wall -Wextra -o "$@" "$<"

eth-rf-caps: $(ETH_RF_SENDER)
	@sudo setcap cap_net_raw,cap_net_admin+ep "$(ETH_RF_SENDER)"
	@getcap "$(ETH_RF_SENDER)"

eth-rf-send: $(ETH_RF_SENDER)
	@"$(ETH_RF_SENDER)" \
	  --iface "$(ETH_RF_IFACE)" \
	  --input "$(ETH_RF_INPUT)" \
	  --sample-format "$(ETH_RF_SAMPLE_FORMAT)" \
	  --chunk-samples "$(ETH_RF_CHUNK_SAMPLES)" \
	  --mtu "$(ETH_RF_MTU)" \
	  --preload-samples "$(ETH_RF_PRELOAD_SAMPLES)" \
	  --max-samples "$(ETH_RF_MAX_SAMPLES)" \
	  --max-bytes "$(ETH_RF_MAX_BYTES)"

eth-rf-stop: $(ETH_RF_SENDER)
	@"$(ETH_RF_SENDER)" \
	  --iface "$(ETH_RF_IFACE)" \
	  --mtu "$(ETH_RF_MTU)" \
	  --stop-only

eth-rf-send-python:
	@python3 "$(FPGA_DIR)/flow/ethernet_rf/send_gps_rf.py" \
	  --iface "$(ETH_RF_IFACE)" \
	  --input "$(ETH_RF_INPUT)" \
	  --sample-format "$(ETH_RF_SAMPLE_FORMAT)" \
	  --chunk-samples "$(ETH_RF_CHUNK_SAMPLES)" \
	  --mtu "$(ETH_RF_MTU)" \
	  --preload-samples "$(ETH_RF_PRELOAD_SAMPLES)" \
	  --max-samples "$(ETH_RF_MAX_SAMPLES)" \
	  --max-bytes "$(ETH_RF_MAX_BYTES)"

clean-sw:
	@$(MAKE_BIN) -C "$(SOC_SW_DIR)" clean

clean-fpga:
	@rm -rf "$(OUT_DIR)/build" "$(OUT_DIR)/bitstream" "$(OUT_DIR)/reports" "$(OUT_DIR)/logs"

clean-flash:
	@rm -rf "$(OUT_DIR)/flash"

prune:
	@rm -rf "$(OUT_DIR)/logs" "$(OUT_DIR)/reports" "$(OUT_DIR)/build/checkpoints"
	rm -f "$(OUT_DIR)/flash/vcu118_rocket.mcs" "$(OUT_DIR)/flash/vcu118_rocket.bin" "$(OUT_DIR)/flash/vcu118_rocket.prm"
	rm -f "$(OUT_DIR)/flash/regions/bitstream.mcs" "$(OUT_DIR)/flash/regions/bitstream.prm"
	find "$(OUT_DIR)" -type d -empty -delete 2>/dev/null || true

clean: clean-sw
	@rm -rf "$(OUT_DIR)" "$(FIRRTL_DIR)" "$(VERILOG_DIR)"
