#!/bin/bash
# Copyright lowRISC contributors (OpenTitan project).
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0


# Utility script to load ROM contents into FPGA bitstream.
# Usage:
#   cd $REPO_TOP
#   ./util/fpga/splice_rom.sh
#
# The updated bitstream is located at the same place as the original Vivado bitstream e.g. at
# $REPO_TOP/build/lowrisc_systems_chip_egret_cw310_0.1/synth-vivado/
#  lowrisc_systems_chip_egret_cw310_0.1.bit
#
# A copy of the original bitstream is created at e.g. at
# $REPO_TOP/build/lowrisc_systems_chip_egret_cw310_0.1/synth-vivado/
#  lowrisc_systems_chip_egret_cw310_0.1.bit.orig
set -e

. util/build_consts.sh

usage() {
  cat << USAGE
Utility script to load ROM contents into the FPGA bitstream.

Usage: $0 [-t TARGET_BOARD] [-T TARGET_TOP] [-b DV | PROD]

  - t: Target board: cw310, cw305.
  - T: Target top: egret.
  - b: ROM binary, set to either DV or PROD.

ROM binary targets (-b):
  - DV: sw/device/lib/testing/test_rom
  - PROD: sw/device/silicon_creator/rom

USAGE
}

# Change these variables when using the script for a different top level and/or FPGA board.
FLAGS_TARGET_BOARD="cw310"
FLAGS_TARGET_TOP="egret"
FLAGS_BIN="DV"

# `getopts` usage
# - The initial colon in the optstring is to suppress the default error
#   handling.
# - The remaining options are specified in alphabetical order, and the case
#   statement should match this order.
# - Only options that take an argument should have a following colon.
# - The case statement contains two additional cases:
#   - when `$flag` = `?`, this is an unexpected option.
#   - when `$flag` = `:`, this is the case that a flag which requires an
#     argument is not provided one. In both cases, `$OPTARG` contains the
#     relevant parsed option.
# - After option parsing is finished, we `shift` by `$OPTIND - 1` so that the
#   remaining (unprocessed) arguments are in `$@` (and $1, $2, $3 etc.).
while getopts ':b:t:T:' flag; do
  case "${flag}" in
    b) FLAGS_BIN="${OPTARG}";;
    t) FLAGS_TARGET_BOARD="${OPTARG}";;
    T) FLAGS_TARGET_TOP="${OPTARG}";;
    \?) echo "Unexpected option: -${OPTARG}" >&2
        usage
        exit 1
        ;;
    :) echo "Option -${OPTARG} requires an argument" >&2
       usage
       exit 1
       ;;
    *) echo "Internal Error: Unhandled option: -${flag}" >&2
       exit 1
       ;;
  esac
done
shift $((OPTIND - 1))

# We do not accept additional arguments.
if [[ "$#" -gt 0 ]]; then
  echo "Unexpected arguments:" "$@" >&2
  exit 1
fi

TARGET_PREFIX=""
if [[ ${FLAGS_BIN} == "DV" ]]; then
  TARGET_PREFIX="sw/device/lib/testing/test_rom/test_rom"
elif [[ ${FLAGS_BIN} == "PROD" ]]; then
  TARGET_PREFIX="sw/device/silicon_creator/rom/rom"
else
  echo "Invalid -b option: ${FLAGS_BIN}; expected DV or PROD." >&2
  exit 1
fi

TARGET_BOARD="${FLAGS_TARGET_BOARD}"
TARGET_TOP="${FLAGS_TARGET_TOP}"
TARGET_FILE_EXT=".scr.39.vmem"
TARGET="${BIN_DIR}/${TARGET_PREFIX}_fpga_${TARGET_BOARD}"
TARGET_PATH="${TARGET}${TARGET_FILE_EXT}"


VLNV_PATH="lowrisc_systems_chip_${TARGET_TOP}_${TARGET_BOARD}_0.1"

FPGA_BIN_DIR="${BIN_DIR}/hw/${VLNV_PATH}/synth-vivado"
FPGA_BIT_NAME="$VLNV_PATH"

# Make sure all inputs are available.
if [[ ! -f "${TARGET_PATH}" ]]; then
  echo "Unable to find ROM base image ${TARGET_PATH}." >&2
  exit 1
fi

if [[ ! -f "${FPGA_BIN_DIR}/memories.mmi" ]]; then
  echo "Unable to find ${FPGA_BIN_DIR}/memories.mmi." >&2
  exit 1
fi

if [[ ! -f "${FPGA_BIN_DIR}/${FPGA_BIT_NAME}.bit" ]]; then
  echo "Unable to find ${FPGA_BIN_DIR}/${FPGA_BIT_NAME}.bit." >&2
  exit 1
fi

# Create the Vivado image for splicing.
hw/ip/rom_ctrl/util/gen_vivado_mem_image.py \
  "${TARGET_PATH}" \
  "${TARGET}.updatemem.mem" \
  --swap-nibbles

# Splice the ROM.
# The --debug flag is undocumented and causes updatemem to print out the INIT_XX
# values of the four BRAM cells. These values are also observable when opening
# the implemented design in Vivado and then inspecting the cell properties of
# the corresponding BRAM cells. This information is very useful when debugging
# the splicing flow.
updatemem -force --meminfo "${FPGA_BIN_DIR}/meomries.mmi" \
  --data "${TARGET}.updatemem.mem" \
  --bit "${FPGA_BIN_DIR}/${FPGA_BIT_NAME}.bit"  --proc rom \
  --out "${FPGA_BIN_DIR}/${FPGA_BIT_NAME}.splice.bit" \
  --debug

mv ${FPGA_BIN_DIR}/${FPGA_BIT_NAME}.bit ${FPGA_BIN_DIR}/${FPGA_BIT_NAME}.bit.orig

# Rename to the canonical bitstream output name to simplify interaction with
# other tools, and create a copy with a .splice suffix to be able to
# export the artifact in CI.
mv ${FPGA_BIN_DIR}/${FPGA_BIT_NAME}.splice.bit ${FPGA_BIN_DIR}/${FPGA_BIT_NAME}.bit
cp ${FPGA_BIN_DIR}/${FPGA_BIT_NAME}.bit ${FPGA_BIN_DIR}/${FPGA_BIT_NAME}.bit.splice
