#!/usr/bin/env bash

libs=()
genvcd=false
use_xsim=false
use_modelsim=false
verbose=false
keeprunning=false
makejmode=false
frontend="verilog -noblackbox"
backend_opts="-noattr -noexpr -siminit"
autotb_opts=""
include_opts=""
xinclude_opts=""
minclude_opts=""
scriptfiles=""
scriptopt=""
toolsdir="$(cd "$(dirname "$0")"; pwd)"
warn_iverilog_git=false
# The following are used in verilog to firrtl regression tests.
# Typically these will be passed as environment variables:
#EXTRA_FLAGS="--firrtl2verilog 'java -cp /.../firrtl/utils/bin/firrtl.jar firrtl.Driver'"
# The tests are skipped if firrtl2verilog is the empty string (the default).
firrtl2verilog=""
xfirrtl="../xfirrtl"
abcprog="$toolsdir/../../yosys-abc"

if [ ! -f "$toolsdir/cmp_tbdata" -o "$toolsdir/cmp_tbdata.c" -nt "$toolsdir/cmp_tbdata" ]; then
	( set -ex; ${CXX:-g++} -Wall -o "$toolsdir/cmp_tbdata" "$toolsdir/cmp_tbdata.c"; ) || exit 1
fi

while getopts xmGl:wkjvref:s:p:n:S:I:A:-: opt; do
	case "$opt" in
		x)
			use_xsim=true ;;
		m)
			use_modelsim=true ;;
		G)
			warn_iverilog_git=true ;;
		l)
			libs+=("$(cd "$(dirname "$OPTARG")"; pwd)/$(basename "$OPTARG")");;
		w)
			genvcd=true ;;
		k)
			keeprunning=true ;;
		j)
			makejmode=true ;;
		v)
			verbose=true ;;
		r)
			backend_opts="$backend_opts -norename" ;;
		e)
			backend_opts="$( echo " $backend_opts " | sed 's, -noexpr , ,; s,^ ,,; s, $,,;'; )" ;;
		f)
			frontend="$OPTARG" ;;
		s)
			[[ "$OPTARG" == /* ]] || OPTARG="$PWD/$OPTARG"
			scriptfiles="$scriptfiles $OPTARG" ;;
		p)
			scriptopt="$OPTARG" ;;
		n)
			autotb_opts="$autotb_opts -n $OPTARG" ;;
		S)
			autotb_opts="$autotb_opts -seed $OPTARG" ;;
		I)
			include_opts="$include_opts -I $OPTARG"
			xinclude_opts="$xinclude_opts -i $OPTARG"
			minclude_opts="$minclude_opts +incdir+$OPTARG" ;;
		A)
			abcprog="$OPTARG" ;;
		-)
			case "${OPTARG}" in
			    xfirrtl)
			    	xfirrtl="${!OPTIND}"
				OPTIND=$(( $OPTIND + 1 ))
				;;
			    firrtl2verilog)
			    	firrtl2verilog="${!OPTIND}"
				OPTIND=$(( $OPTIND + 1 ))
				;;
			    *)
			    	if [ "$OPTERR" == 1 ] && [ "${optspec:0:1}" != ":" ]; then
				    echo "Unknown option --${OPTARG}" >&2
				fi
				;;
			esac;;
		*)
			echo "Usage: $0 [-x|-m] [-G] [-w] [-k] [-j] [-v] [-r] [-e] [-l libs] [-f frontend] [-s script] [-p cmdstring] [-n iters] [-S seed] [-I incdir] [--xfirrtl FIRRTL test exclude file] [--firrtl2verilog command to generate verilog from firrtl] verilog-files\n" >&2
			exit 1
	esac
done

compile_and_run() {
	exe="$1"; output="$2"; shift 2
	if [ "${2##*.}" == "sv" ]; then
		language_gen="-g2012"
	else
		language_gen="-g2005"
	fi

	if $use_modelsim; then
		altver=$( ls -v /opt/altera/ | grep '^[0-9]' | tail -n1; )
		/opt/altera/$altver/modelsim_ase/bin/vlib work
		/opt/altera/$altver/modelsim_ase/bin/vlog $minclude_opts +define+outfile=\"$output\" "$@"
		/opt/altera/$altver/modelsim_ase/bin/vsim -c -do 'run -all; exit;' testbench
	elif $use_xsim; then
		xilver=$( ls -v /opt/Xilinx/Vivado/ | grep '^[0-9]' | tail -n1; )
		/opt/Xilinx/Vivado/$xilver/bin/xvlog $xinclude_opts -d outfile=\"$output\" "$@"
		/opt/Xilinx/Vivado/$xilver/bin/xelab -R work.testbench
	else
		iverilog $language_gen $include_opts -Doutfile=\"$output\" -s testbench -o "$exe" "$@"
		vvp -n "$exe"
	fi
}

shift $((OPTIND - 1))

for fn
do
	bn=${fn%.*}
	ext=${fn##*.}
	if [[ "$ext" != "v" ]] && [[ "$ext" != "sv" ]] && [[ "$ext" != "aag" ]] && [[ "$ext" != "aig" ]]; then
		echo "Invalid argument: $fn" >&2
		exit 1
	fi
	[[ "$bn" == *_tb ]] && continue

	if $makejmode; then
		status_prefix="Test: $bn "
	else
		status_prefix=""
		echo -n "Test: $bn "
	fi

	if [ "$ext" == sv ]; then
		frontend="$frontend -sv"
	fi

	rm -f ${bn}.{err,log,skip}
	mkdir -p ${bn}.out
	rm -rf ${bn}.out/*

	body() {
		cd ${bn}.out
		fn=$(basename $fn)
		bn=$(basename $bn)
		refext=v

		rm -f ${bn}_ref.fir
		if [[ "$ext" == "v" ]]; then
			egrep -v '^\s*`timescale' ../$fn > ${bn}_ref.${ext}
		elif [[ "$ext" == "aig" ]] || [[ "$ext" == "aag" ]]; then
			$abcprog -c "read_aiger ../${fn}; write ${bn}_ref.${refext}"
		else
			refext=$ext
			cp ../${fn} ${bn}_ref.${refext}
		fi

		if [ ! -f ../${bn}_tb.v ]; then
			"$toolsdir"/../../yosys -f "$frontend $include_opts -D_AUTOTB" -b "test_autotb $autotb_opts" -o ${bn}_tb.v ${bn}_ref.${refext}
		else
			cp ../${bn}_tb.v ${bn}_tb.v
		fi
		if $genvcd; then sed -i 's,// \$dump,$dump,g' ${bn}_tb.v; fi
		touch ${bn}.iverilog
		compile_and_run ${bn}_tb_ref ${bn}_out_ref ${bn}_tb.v ${bn}_ref.${refext} "${libs[@]}" \
					"$toolsdir"/../../techlibs/common/simlib.v \
					"$toolsdir"/../../techlibs/common/simcells.v
		rm ${bn}.iverilog
		if $genvcd; then mv testbench.vcd ${bn}_ref.vcd; fi

		test_count=0
		test_passes() {
			"$toolsdir"/../../yosys -b "verilog $backend_opts" -o ${bn}_syn${test_count}.v "$@"
			touch ${bn}.iverilog
			compile_and_run ${bn}_tb_syn${test_count} ${bn}_out_syn${test_count} \
					${bn}_tb.v ${bn}_syn${test_count}.v "${libs[@]}" \
					"$toolsdir"/../../techlibs/common/simlib.v \
					"$toolsdir"/../../techlibs/common/simcells.v
			if $genvcd; then mv testbench.vcd ${bn}_syn${test_count}.vcd; fi
			"$toolsdir/cmp_tbdata" ${bn}_out_ref ${bn}_out_syn${test_count}
			rm ${bn}.iverilog
			test_count=$(( test_count + 1 ))
		}

		if [ "$frontend" = "verific" -o "$frontend" = "verific_gates" ] && grep -q VERIFIC-SKIP ${bn}_ref.${refext}; then
			touch ../${bn}.skip
			return
		fi

		if [ -n "$scriptfiles" ]; then
			test_passes -f "$frontend $include_opts" ${bn}_ref.${refext} $scriptfiles
		elif [ -n "$scriptopt" ]; then
			test_passes -f "$frontend $include_opts" -p "$scriptopt" ${bn}_ref.${refext}
		elif [ "$frontend" = "verific" ]; then
			test_passes -p "verific -vlog2k ${bn}_ref.${refext}; verific -import -all; opt; memory;;"
		elif [ "$frontend" = "verific_gates" ]; then
			test_passes -p "verific -vlog2k ${bn}_ref.${refext}; verific -import -gates -all; opt; memory;;"
		else
			test_passes -f "$frontend $include_opts" -p "hierarchy; proc; opt -nodffe -nosdff; fsm; opt; memory; opt -full -fine" ${bn}_ref.${refext}
			test_passes -f "$frontend $include_opts" -p "hierarchy; synth -run coarse; techmap; opt; abc -dff" ${bn}_ref.${refext}
			if [ -n "$firrtl2verilog" ]; then
			    if test -z "$xfirrtl" || ! grep "$fn" "$xfirrtl" ; then
				"$toolsdir"/../../yosys -b "firrtl" -o ${bn}_ref.fir -f "$frontend $include_opts" -p "prep; proc; opt -nodffe -nosdff; fsm; opt; memory; opt -full -fine; pmuxtree" ${bn}_ref.${refext}
				$firrtl2verilog -i ${bn}_ref.fir -o ${bn}_ref.fir.v
				test_passes -f "$frontend $include_opts" -p "hierarchy; proc; opt -nodffe -nosdff; fsm; opt; memory; opt -full -fine" ${bn}_ref.fir.v
			    fi
			fi
		fi
		touch ../${bn}.log
	}

	if $verbose; then
		echo ".."
		echo "Output written to console." > ${bn}.err
		( set -ex; body; )
	else
		( set -ex; body; ) > ${bn}.err 2>&1
	fi

	did_firrtl=""
	if [ -f ${bn}.out/${bn}_ref.fir ]; then
	    did_firrtl="+FIRRTL "
	fi
	if [ -f ${bn}.log ]; then
		mv ${bn}.err ${bn}.log
		echo "${status_prefix}${did_firrtl}-> ok"
	elif [ -f ${bn}.skip ]; then
		mv ${bn}.err ${bn}.skip
		echo "${status_prefix}-> skip"
	else
		echo "${status_prefix}${did_firrtl}-> ERROR!"
		if $warn_iverilog_git; then
			if [ -f ${bn}.out/${bn}.iverilog ]; then
				echo "Note: Make sure that 'iverilog' is an up-to-date git checkout of Icarus Verilog."
			fi
		fi
		$keeprunning || exit 1
	fi
done

exit 0
