#! /usr/bin/env bash

# check that the oeb pin is low/high based on io_in and io_out connections

# Uses lvs_config to optionally extract a spice netlist
# Requires a netlist $WORK_ROOT/ext/$DESIGN_NAME.gds.spice
# Converts this netlist to $WORK_ROOT/ext/$DESIGN_NAME.cdl.gz

#   Copyright 2023 D. Mitch Bailey  cvc at shuharisystem dot com

#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

# Parameters are read from $WORK_ROOT/cvcrc.oeb which is copied from the default in $LVS_ROOT if non-existent.

# Return codes
# 1: LVS_ROOT not set
# 2: Missing file
# 3: Unrecognized block name
# 4: CVC execution error
# 5: OEB related error
# 6: OEB related warning

# Use cases
# run_oeb_check [--noextract] [lvs_config_file [top_layout [layout_file]]]

extract_flag=
if [[ $1 == "--noextract" ]]; then
	export EXTRACT_LAYOUT=no
	extract_flag="$1"
	shift
else
	export EXTRACT_LAYOUT=yes
fi

usage="usage: run_oeb_check [--noextract] [lvs_config_file [top_layout [layout_file]]]]"
if [[ $# -gt 3 ]]; then
	echo $usage
	exit 1
fi

# Check for LVS_ROOT
if [[ -z "$LVS_ROOT" ]]; then
	echo "LVS_ROOT not set."
	exit 1
fi

CONFIG_FILE=$1
DESIGN_NAME=${2:+"-d $2"}

if [[ $# -ne 0 ]]; then # if config file not specified, skip and use current environment
	source <($LVS_ROOT/set_lvs_env.py -c $CONFIG_FILE $DESIGN_NAME)
fi
if [[ ! -v EXTRACT_FLATGLOB ]]; then
	echo "ERROR: LVS environment problem."
	exit 1
fi
export TOP_LAYOUT=${2:-$TOP_LAYOUT}
export LAYOUT_FILE=${3:-$LAYOUT_FILE}

export DESIGN_NAME=$TOP_LAYOUT

if [[ $DESIGN_NAME == *openframe_project_wrapper ]]; then
	# OEB checks for openframe designs are done in a different program with the current environment
	exec $LVS_ROOT/run_openframe_check $extract_flag
fi

echo "DESIGN NAME: $DESIGN_NAME"

echo "WORK_ROOT   : ${WORK_ROOT:=$(pwd)/$DESIGN_NAME}"
echo "LOG_ROOT    : ${LOG_ROOT:=$(pwd)/$DESIGN_NAME}"
echo "SIGNOFF_ROOT: ${SIGNOFF_ROOT:=$(pwd)/$DESIGN_NAME}"
export LOG_ROOT SIGNOFF_ROOT WORK_ROOT

mkdir -p $LOG_ROOT $SIGNOFF_ROOT $WORK_ROOT
export RESULTS_DIR=$WORK_ROOT/ext

rm -f $WORK_ROOT/cvc.oeb* $LOG_ROOT/cvc.oeb* $SIGNOFF_ROOT/cvc.oeb*

export RESULTS_DIR=$WORK_ROOT/ext
if [[ $EXTRACT_LAYOUT == yes ]]; then
	env RUN_DIR=$RESULTS_DIR CIFIN_STYLE= EXTRACT_STYLE= LOG_FILE=ext.log $LVS_ROOT/run_extract
	ext_status=$?
	gzip -c -f $RESULTS_DIR/$DESIGN_NAME.gds.spice > $SIGNOFF_ROOT/$DESIGN_NAME.gds.spice.gz
fi

echo " "
echo "Running CVC for oeb check..."

# Create cdl file from extracted spice file if it doesn't exist
if [[ ! -f $WORK_ROOT/$DESIGN_NAME.cdl.gz || $SIGNOFF_ROOT/$DESIGN_NAME.gds.spice.gz -nt $WORK_ROOT/$DESIGN_NAME.cdl.gz ]]; then
	if [[ ! -f $SIGNOFF_ROOT/$DESIGN_NAME.gds.spice.gz ]]; then
		echo "Could not create cdl file from $SIGNOFF_ROOT/$DESIGN_NAME.gds.spice.gz"
		exit 2
	else
		echo "Creating $WORK_ROOT/$DESIGN_NAME.cdl.gz"
		gunzip -c $SIGNOFF_ROOT/$DESIGN_NAME.gds.spice.gz |
			$LVS_ROOT/tech/$PDK/spi2cdl - |
			gzip -c >$WORK_ROOT/$DESIGN_NAME.cdl.gz
	fi
fi

# Copy default power file to work area if it doesn't exist
# shopt -u nocasematch
# BASE_NAME=${DESIGN_NAME//[A-Z0-9][A-Z0-9]_}  # remove leading 2 byte prefices
if [[ ! -f $WORK_ROOT/cvc.power.$DESIGN_NAME ]]; then
	if [[ -f $LVS_ROOT/tech/$PDK/cvc.power.$DESIGN_NAME ]]; then
		cp $LVS_ROOT/tech/$PDK/cvc.power.$DESIGN_NAME $WORK_ROOT/cvc.power.$DESIGN_NAME
	else
		echo "
ERROR: Could not find $WORK_ROOT/cvc.power.$DESIGN_NAME"
		exit 2
	fi
fi

if [[ ! -f $WORK_ROOT/cvcrc.oeb ]]; then
	if [[ ! -f $WORK_ROOT/cvcrc ]]; then
		if [[ -f $LVS_ROOT/tech/$PDK/cvcrc ]]; then
			cp $LVS_ROOT/tech/$PDK/cvcrc $WORK_ROOT/cvcrc
		else
			echo "
ERROR: Could not create $WORK_ROOT/cvcrc.oeb"
			exit 2
		fi
	fi
	sed 's/cvc.log/cvc.oeb.log/' $WORK_ROOT/cvcrc >$WORK_ROOT/cvcrc.oeb
fi

if [[ $DESIGN_NAME == *user_analog_project_wrapper ]]; then
	PADRING_TYPE=caravan
	cat >$WORK_ROOT/cvc.oeb.script <<-cvcin
		c 6
		gn io_in[0]
		gn io_in[1]
		gn io_in[2]
		gn io_in[3]
		gn io_in[4]
		gn io_in[5]
		gn io_in[6]
		gn io_in[7]
		gn io_in[8]
		gn io_in[9]
		gn io_in[10]
		gn io_in[11]
		gn io_in[12]
		gn io_in[13]
		gn io_in[14]
		gn io_in[15]
		gn io_in[16]
		gn io_in[17]
		gn io_in[18]
		gn io_in[19]
		gn io_in[20]
		gn io_in[21]
		gn io_in[22]
		gn io_in[23]
		gn io_in[24]
		gn io_in[25]
		gn io_in[26]
		gn io_in_3v3[0]
		gn io_in_3v3[1]
		gn io_in_3v3[2]
		gn io_in_3v3[3]
		gn io_in_3v3[4]
		gn io_in_3v3[5]
		gn io_in_3v3[6]
		gn io_in_3v3[7]
		gn io_in_3v3[8]
		gn io_in_3v3[9]
		gn io_in_3v3[10]
		gn io_in_3v3[11]
		gn io_in_3v3[12]
		gn io_in_3v3[13]
		gn io_in_3v3[14]
		gn io_in_3v3[15]
		gn io_in_3v3[16]
		gn io_in_3v3[17]
		gn io_in_3v3[18]
		gn io_in_3v3[19]
		gn io_in_3v3[20]
		gn io_in_3v3[21]
		gn io_in_3v3[22]
		gn io_in_3v3[23]
		gn io_in_3v3[24]
		gn io_in_3v3[25]
		gn io_in_3v3[26]
		gn io_out[0]
		gn io_out[1]
		gn io_out[2]
		gn io_out[3]
		gn io_out[4]
		gn io_out[5]
		gn io_out[6]
		gn io_out[7]
		gn io_out[8]
		gn io_out[9]
		gn io_out[10]
		gn io_out[11]
		gn io_out[12]
		gn io_out[13]
		gn io_out[14]
		gn io_out[15]
		gn io_out[16]
		gn io_out[17]
		gn io_out[18]
		gn io_out[19]
		gn io_out[20]
		gn io_out[21]
		gn io_out[22]
		gn io_out[23]
		gn io_out[24]
		gn io_out[25]
		gn io_out[26]
		gn io_oeb[0]
		gn io_oeb[1]
		gn io_oeb[2]
		gn io_oeb[3]
		gn io_oeb[4]
		gn io_oeb[5]
		gn io_oeb[6]
		gn io_oeb[7]
		gn io_oeb[8]
		gn io_oeb[9]
		gn io_oeb[10]
		gn io_oeb[11]
		gn io_oeb[12]
		gn io_oeb[13]
		gn io_oeb[14]
		gn io_oeb[15]
		gn io_oeb[16]
		gn io_oeb[17]
		gn io_oeb[18]
		gn io_oeb[19]
		gn io_oeb[20]
		gn io_oeb[21]
		gn io_oeb[22]
		gn io_oeb[23]
		gn io_oeb[24]
		gn io_oeb[25]
		gn io_oeb[26]
		gn gpio_analog[0]
		gn gpio_analog[1]
		gn gpio_analog[2]
		gn gpio_analog[3]
		gn gpio_analog[4]
		gn gpio_analog[5]
		gn gpio_analog[6]
		gn gpio_analog[7]
		gn gpio_analog[8]
		gn gpio_analog[9]
		gn gpio_analog[10]
		gn gpio_analog[11]
		gn gpio_analog[12]
		gn gpio_analog[13]
		gn gpio_analog[14]
		gn gpio_analog[15]
		gn gpio_analog[16]
		gn gpio_analog[17]
		gn gpio_noesd[0]
		gn gpio_noesd[1]
		gn gpio_noesd[2]
		gn gpio_noesd[3]
		gn gpio_noesd[4]
		gn gpio_noesd[5]
		gn gpio_noesd[6]
		gn gpio_noesd[7]
		gn gpio_noesd[8]
		gn gpio_noesd[9]
		gn gpio_noesd[10]
		gn gpio_noesd[11]
		gn gpio_noesd[12]
		gn gpio_noesd[13]
		gn gpio_noesd[14]
		gn gpio_noesd[15]
		gn gpio_noesd[16]
		gn gpio_noesd[17]
		q
	cvcin
elif [[ $DESIGN_NAME == *user_project_wrapper ]]; then
	PADRING_TYPE=caravel
	cat >$WORK_ROOT/cvc.oeb.script <<-cvcin
		c 6
		gn io_in[0]
		gn io_in[1]
		gn io_in[2]
		gn io_in[3]
		gn io_in[4]
		gn io_in[5]
		gn io_in[6]
		gn io_in[7]
		gn io_in[8]
		gn io_in[9]
		gn io_in[10]
		gn io_in[11]
		gn io_in[12]
		gn io_in[13]
		gn io_in[14]
		gn io_in[15]
		gn io_in[16]
		gn io_in[17]
		gn io_in[18]
		gn io_in[19]
		gn io_in[20]
		gn io_in[21]
		gn io_in[22]
		gn io_in[23]
		gn io_in[24]
		gn io_in[25]
		gn io_in[26]
		gn io_in[27]
		gn io_in[28]
		gn io_in[29]
		gn io_in[30]
		gn io_in[31]
		gn io_in[32]
		gn io_in[33]
		gn io_in[34]
		gn io_in[35]
		gn io_in[36]
		gn io_in[37]
		gn io_out[0]
		gn io_out[1]
		gn io_out[2]
		gn io_out[3]
		gn io_out[4]
		gn io_out[5]
		gn io_out[6]
		gn io_out[7]
		gn io_out[8]
		gn io_out[9]
		gn io_out[10]
		gn io_out[11]
		gn io_out[12]
		gn io_out[13]
		gn io_out[14]
		gn io_out[15]
		gn io_out[16]
		gn io_out[17]
		gn io_out[18]
		gn io_out[19]
		gn io_out[20]
		gn io_out[21]
		gn io_out[22]
		gn io_out[23]
		gn io_out[24]
		gn io_out[25]
		gn io_out[26]
		gn io_out[27]
		gn io_out[28]
		gn io_out[29]
		gn io_out[30]
		gn io_out[31]
		gn io_out[32]
		gn io_out[33]
		gn io_out[34]
		gn io_out[35]
		gn io_out[36]
		gn io_out[37]
		gn io_oeb[0]
		gn io_oeb[1]
		gn io_oeb[2]
		gn io_oeb[3]
		gn io_oeb[4]
		gn io_oeb[5]
		gn io_oeb[6]
		gn io_oeb[7]
		gn io_oeb[8]
		gn io_oeb[9]
		gn io_oeb[10]
		gn io_oeb[11]
		gn io_oeb[12]
		gn io_oeb[13]
		gn io_oeb[14]
		gn io_oeb[15]
		gn io_oeb[16]
		gn io_oeb[17]
		gn io_oeb[18]
		gn io_oeb[19]
		gn io_oeb[20]
		gn io_oeb[21]
		gn io_oeb[22]
		gn io_oeb[23]
		gn io_oeb[24]
		gn io_oeb[25]
		gn io_oeb[26]
		gn io_oeb[27]
		gn io_oeb[28]
		gn io_oeb[29]
		gn io_oeb[30]
		gn io_oeb[31]
		gn io_oeb[32]
		gn io_oeb[33]
		gn io_oeb[34]
		gn io_oeb[35]
		gn io_oeb[36]
		gn io_oeb[37]
		gn io_in[0]
		gn io_in[1]
		gn io_in[2]
		gn io_in[3]
		gn io_in[4]
		gn io_in[5]
		gn io_in[6]
		gn io_in[7]
		gn io_in[8]
		gn io_in[9]
		gn io_in[10]
		gn io_in[11]
		gn io_in[12]
		gn io_in[13]
		gn io_in[14]
		gn io_in[15]
		gn io_in[16]
		gn io_in[17]
		gn io_in[18]
		gn io_in[19]
		gn io_in[20]
		gn io_in[21]
		gn io_in[22]
		gn io_in[23]
		gn io_in[24]
		gn io_in[25]
		gn io_in[26]
		gn io_in[27]
		gn io_in[28]
		gn io_in[29]
		gn io_in[30]
		gn io_in[31]
		gn io_in[32]
		gn io_in[33]
		gn io_in[34]
		gn io_in[35]
		gn io_in[36]
		gn io_in[37]
		gn analog_io[0]
		gn analog_io[1]
		gn analog_io[2]
		gn analog_io[3]
		gn analog_io[4]
		gn analog_io[5]
		gn analog_io[6]
		gn analog_io[7]
		gn analog_io[8]
		gn analog_io[9]
		gn analog_io[10]
		gn analog_io[11]
		gn analog_io[12]
		gn analog_io[13]
		gn analog_io[14]
		gn analog_io[15]
		gn analog_io[16]
		gn analog_io[17]
		gn analog_io[18]
		gn analog_io[19]
		gn analog_io[20]
		gn analog_io[21]
		gn analog_io[22]
		gn analog_io[23]
		gn analog_io[24]
		gn analog_io[25]
		gn analog_io[26]
		gn analog_io[27]
		gn analog_io[28]
		q
	cvcin
elif [[ $DESIGN_NAME == *user_project_wrapper_mini4 ]]; then
	cat >$WORK_ROOT/cvc.oeb.script <<-cvcin
		c 6
		gn io_in[0]
		gn io_in[1]
		gn io_in[2]
		gn io_in[3]
		gn io_in[4]
		gn io_in[5]
		gn io_in[6]
		gn io_in[7]
		gn io_in[8]
		gn io_in[9]
		gn io_in[10]
		gn io_in[11]
		gn io_in[12]
		gn io_in[13]
		gn io_in[14]
		gn io_in[15]
		gn io_in[16]
		gn io_in[17]
		gn io_in[18]
		gn io_in[19]
		gn io_in[20]
		gn io_in[21]
		gn io_in[22]
		gn io_in[23]
		gn io_in[24]
		gn io_in[25]
		gn io_in[26]
		gn io_in[27]
		gn io_in[28]
		gn io_in[29]
		gn io_in[30]
		gn io_in[31]
		gn io_in[32]
		gn io_in[33]
		gn io_in[34]
		gn io_in[35]
		gn io_out[0]
		gn io_out[1]
		gn io_out[2]
		gn io_out[3]
		gn io_out[4]
		gn io_out[5]
		gn io_out[6]
		gn io_out[7]
		gn io_out[8]
		gn io_out[9]
		gn io_out[10]
		gn io_out[11]
		gn io_out[12]
		gn io_out[13]
		gn io_out[14]
		gn io_out[15]
		gn io_out[16]
		gn io_out[17]
		gn io_out[18]
		gn io_out[19]
		gn io_out[20]
		gn io_out[21]
		gn io_out[22]
		gn io_out[23]
		gn io_out[24]
		gn io_out[25]
		gn io_out[26]
		gn io_out[27]
		gn io_out[28]
		gn io_out[29]
		gn io_out[30]
		gn io_out[31]
		gn io_out[32]
		gn io_out[33]
		gn io_out[34]
		gn io_out[35]
		gn io_oeb[0]
		gn io_oeb[1]
		gn io_oeb[2]
		gn io_oeb[3]
		gn io_oeb[4]
		gn io_oeb[5]
		gn io_oeb[6]
		gn io_oeb[7]
		gn io_oeb[8]
		gn io_oeb[9]
		gn io_oeb[10]
		gn io_oeb[11]
		gn io_oeb[12]
		gn io_oeb[13]
		gn io_oeb[14]
		gn io_oeb[15]
		gn io_oeb[16]
		gn io_oeb[17]
		gn io_oeb[18]
		gn io_oeb[19]
		gn io_oeb[20]
		gn io_oeb[21]
		gn io_oeb[22]
		gn io_oeb[23]
		gn io_oeb[24]
		gn io_oeb[25]
		gn io_oeb[26]
		gn io_oeb[27]
		gn io_oeb[28]
		gn io_oeb[29]
		gn io_oeb[30]
		gn io_oeb[31]
		gn io_oeb[32]
		gn io_oeb[33]
		gn io_oeb[34]
		gn io_oeb[35]
		gn io_in[0]
		gn io_in[1]
		gn io_in[2]
		gn io_in[3]
		gn io_in[4]
		gn io_in[5]
		gn io_in[6]
		gn io_in[7]
		gn io_in[8]
		gn io_in[9]
		gn io_in[10]
		gn io_in[11]
		gn io_in[12]
		gn io_in[13]
		gn io_in[14]
		gn io_in[15]
		gn io_in[16]
		gn io_in[17]
		gn io_in[18]
		gn io_in[19]
		gn io_in[20]
		gn io_in[21]
		gn io_in[22]
		gn io_in[23]
		gn io_in[24]
		gn io_in[25]
		gn io_in[26]
		gn io_in[27]
		gn io_in[28]
		gn io_in[29]
		gn io_in[30]
		gn io_in[31]
		gn io_in[32]
		gn io_in[33]
		gn io_in[34]
		gn io_in[35]
		q
	cvcin
else
	echo "ERROR: unrecognized top block $DESIGN_NAME"
	exit 3
fi

start_time=$SECONDS
cvc_rv -i $WORK_ROOT/cvcrc.oeb <$WORK_ROOT/cvc.oeb.script

cvc_status=$?

runtime=$((SECONDS - start_time))
hours=$((runtime / 3600))
minutes=$(((runtime % 3600) / 60))
seconds=$(((runtime % 3600) % 60))
printf "Runtime: %d:%02d:%02d (hh:mm:ss)\n" $hours $minutes $seconds >>$WORK_ROOT/cvc.oeb.log

if [[ $WORK_ROOT != $LOG_ROOT ]]; then
	cp $WORK_ROOT/cvc.oeb.log $LOG_ROOT/.
fi

# Remove comments from user_defines.v file
if [[ -f $UPRJ_ROOT/verilog/rtl/user_defines.v ]]; then
	perl -0777 -pe 's://.*?$::mg; s:/\*.*?\*/::sg' $UPRJ_ROOT/verilog/rtl/user_defines.v >$WORK_ROOT/user_defines.v
else
	echo "ERROR: Missing $UPRJ_ROOT/verilog/rtl/user_defines.v"
	touch $WORK_ROOT/user_defines.v
fi

awk -v padring=${PADRING_TYPE:-} '
	BEGIN {
		gpio_config[0] = "FIXED_STD_INPUT_NOPULL";
		gpio_config[1] = "FIXED_STD_INPUT_NOPULL";
		gpio_config[2] = "FIXED_STD_INPUT_NOPULL";
		gpio_config[3] = "FIXED_STD_INPUT_PULLUP";
		gpio_config[4] = "FIXED_STD_INPUT_NOPULL";
	}
	{ sub(/\r$/, ""); } # Remove windows newlines
	/^`define USER_CONFIG_GPIO/ {
		split($2, tokens, /_/); # Ex. USER_CONFIG_GPIO_29_INIT
		gpio_index = tokens[4];
		gpio_config[gpio_index] = gensub(/`GPIO_MODE_/, "", 1, $3); # Save the contstant
	}
	/^> gn io_in/ {
		net = gensub(/.*\[([0-9]*)\]/, "\\1", 1);
		getline;
		getline;
		in_connections[net] += $3 + $5 + $7;
	}
	/^> gn analog_io/ {
		net = gensub(/.*\[([0-9]*)\]/, "\\1", 1);
		getline;
		getline;
		analog_connections[net+7] += $3 + $5 + $7;
	}
	/^> gn gpio_analog/ || /^> gn gpio_noesd/ {
		net = gensub(/.*\[([0-9]*)\]/, "\\1", 1);
		getline;
		getline;
		analog_connections[net+7] += $3 + $5 + $7;
	}
	/^> gn io_out/ {
		net = gensub(/.*\[([0-9]*)\]/, "\\1", 1);
		getline;
		getline;
		out_connections[net] = $3 + $5 + $7;
		# 2 blank lines mark the end of a segment
		while ( NF > 0 ) {
			if ( /^Min path/ ) {
				while ( NF > 0 ) {
					out_min[net] = $1;
					getline;
				}
			} else if ( /^Sim path/ ) {
				while ( NF > 0 ) {
					out_sim[net] = $1;
					getline;
				}
			} else if ( /^Max path/ ) {
				while ( NF > 0 ) {
					out_max[net] = $1;
					getline;
				}
			} else {
				while ( NF > 0 ) {
					getline;
				}
			}
			getline;
		}
		if ( out_min[net] == out_sim[net] && out_sim[net] == out_max[net] && out_min[net] != "" ) {
			fixed_out[net] = out_sim[net];
		}
	}
	/^> gn io_oeb/ {
		net = gensub(/.*\[([0-9]*)\]/, "\\1", 1);
		getline;
		getline;
		oeb_connections[net] = $3 + $5 + $7;
		# 2 blank lines mark the end of a segment
		while ( NF > 0 ) {
			if ( /^Min path/ ) {
				while ( NF > 0 ) {
					oeb_min[net] = $1;
					getline;
				}
			} else if ( /^Sim path/ ) {
				while ( NF > 0 ) {
					oeb_sim[net] = $1;
					getline;
				}
			} else if ( /^Max path/ ) {
				while ( NF > 0 ) {
					oeb_max[net] = $1;
					getline;
				}
			} else {
				while ( NF > 0 ) {
					getline;
				}
			}
			getline;
		}
		if ( oeb_min[net] == oeb_sim[net] && oeb_sim[net] == oeb_max[net] && oeb_min[net] != "" ) {
			fixed_oeb[net] = oeb_sim[net];
		}
	}
	function IsLow(signal) {
		return ( signal ~ /vss/ || signal ~ /VGND/ );
	}
	function IsHigh(signal) {
		return ( signal ~ /vccd/ || signal ~ /vdd/ || signal ~ /VPWR/ );
	}
	END {
		#print "oeb connections:", length(oeb_connections);
		print " gpio/user/analog |   in   |   out  | analog |  oeb min/sim/max  | configuration";
		     #"  xx / xx /  xx   | xxxxxx | xxxxxx | xxxxxx | vxxx*/vxxx*/vxxx* |
		PROCINFO["sorted_in"] = "@ind_num_asc";
		delete messages;
		for ( i in oeb_connections ) {
			gpio = i;
			if ( padring == "caravan" && i+0 > 13 ) {
				gpio += 11;
			}
			analog_i = "  ";
			if ( i+0 >= 7 && i+0 <= ((padring == "caravan") ? 24 : 35 ) ) {
				analog_i = sprintf("%2d", i - 7);
			}
			printf "  %2d / %2d /  %s   | %6s | %6s | %6s | %5s/%5s/%5s | ", \
				gpio, i, analog_i, (in_connections[i] == 0) ? "" : sprintf("%6d", in_connections[i]), \
				(i in fixed_out) ? fixed_out[i] : (out_connections[i] == 0) ? "" : sprintf("%6d", out_connections[i]), \
				(analog_connections[i] == 0) ? "" : sprintf("%6d", analog_connections[i]), \
				oeb_min[i], oeb_sim[i], oeb_max[i];
			# gpio defaults check
			printf "%s ", gpio_config[gpio];
			error_count = length(messages);
			switch(gpio_config[gpio]) {
			case /FIXED_STD_INPUT_.*/: {
				if ( out_connections[i] > 0 && ! (i in fixed_out) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user output connection to fixed input gpio - firmware override required", gpio);
				}
				if ( oeb_connections[i] > 0 && ! (i in fixed_oeb) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user oeb connection to fixed input gpio - firmware override required", gpio);
				}
				if ( analog_connections[i] ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: analog connection to fixed input gpio - firmware override required", gpio);
				}
				break;
			}
			case /MGMT_STD_INPUT_.*/: {
				if ( out_connections[i] > 0 && ! (i in fixed_out) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user output connection to management input gpio ignored", gpio);
				}
				if ( oeb_connections[i] > 0 && ! (i in fixed_oeb) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user oeb connection to management input gpio ignored", gpio);
				}
				if ( analog_connections[i] ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: analog connection to management input gpio", gpio);
				}
				break;
			}
			case /MGMT_STD_OUTPUT/: {
				if ( in_connections[i] > 0 ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user input connection to management output gpio - undriven", gpio);
				}
				if ( out_connections[i] > 0 && ! (i in fixed_out) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user output connection to management output gpio ignored", gpio);
				}
				if ( oeb_connections[i] > 0 && ! (i in fixed_oeb) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user oeb connection to management output gpio ignored", gpio);
				}
				if ( analog_connections[i] ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: analog connection to management output gpio", gpio);
				}
				break;
			}
			case /MGMT_STD_BIDIRECTIONAL/: {
				if ( out_connections[i] > 0 && ! (i in fixed_out) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user output connection to management bidirectional gpio ignored", gpio);
				}
				if ( oeb_connections[i] > 0 && ! (i in fixed_oeb) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user oeb connection to management bidirectional gpio ignored", gpio);
				}
				if ( analog_connections[i] ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: analog connection to management bidirectional gpio", gpio);
				}
				break;
			}
			case /MGMT_STD_ANALOG/: {
				if ( in_connections[i] > 0 ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user input connection to analog gpio - undriven", gpio);
				}
				if ( out_connections[i] > 0 && ! (i in fixed_out) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user output connection to analog gpio ignored", gpio);
				}
				if ( oeb_connections[i] > 0 && ! (i in fixed_oeb) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user oeb connection to analog gpio ignored", gpio);
				}
				break;
			}
			case /USER_STD_INPUT_NOPULL/: {
				if ( in_connections[i] == 0 ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: missing user input connection", gpio);
				}
				if ( out_connections[i] == 0 && ! (i in fixed_out) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user mode requires output connection", gpio);
				} else if ( out_connections[i] > 0 && ! (i in fixed_out) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user output connection to input gpio ignored", gpio);
				}
				if ( oeb_connections[i] == 0 && ! (i in fixed_oeb) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user mode requires oeb connection", gpio);
				} else if ( oeb_connections[i] > 0 && ! (i in fixed_oeb) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user oeb connection to input gpio ignored", gpio);
				}
				if ( analog_connections[i] > 0 ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: analog connection to user input gpio", gpio);
				}
				break;
			}
			case /USER_STD_INPUT_PULLDOWN/: {
				if ( in_connections[i] == 0 ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: missing user input connection", gpio);
				}
				if ( ! IsLow(out_sim[i]) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user pulldown mode requires low output", gpio);
				}
				if ( ! IsLow(oeb_sim[i]) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user pulldown mode requires low oeb", gpio);
				}
				if ( analog_connections[i] ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: analog connection to user input gpio", gpio);
				}
				break;
			}
			case /USER_STD_INPUT_PULLUP/: {
				if ( in_connections[i] == 0 ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: missing user input connection", gpio);
				}
				if ( ! IsHigh(out_sim[i]) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user pullup mode requires high output", gpio);
				}
				if ( ! IsLow(oeb_sim[i]) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user pullup mode requires low oeb", gpio);
				}
				if ( analog_connections[i] ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: analog connection to user input gpio", gpio);
				}
				break;
			}
			case /USER_STD_OUTPUT/: {
				if ( in_connections[i] > 0 ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user input connection to user output gpio - undriven", gpio);
				}
				if ( out_connections[i] == 0 && ! (i in fixed_out) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: missing user output connection for output gpio", gpio);
				} else if ( i in fixed_out ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user output fixed at %s", gpio, fixed_out[i]);
				}
				if ( oeb_connections[i] == 0 && ! (i in fixed_oeb) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: missing user oeb for output gpio", gpio);
				} else if ( IsHigh(oeb_sim[i]) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user oeb is always high - output disabled", gpio);
				} else if ( ! IsLow(oeb_min[i]) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user oeb is never low - output never enabled", gpio);
				}
				if ( analog_connections[i] ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: analog connection to user output gpio", gpio);
				}
				break;
			}
			case /USER_STD_BIDIRECTIONAL/: {
				if ( in_connections[i] == 0 && ! IsLow(oeb_sim[i]) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: missing user input connection to bidirectional gpio", gpio);
				}
				if ( out_connections[i] == 0 && ! (i in fixed_out) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: missing user output connection for bidirectional gpio", gpio);
				} else if ( i in fixed_out && ! IsHigh(oeb_sim[i]) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user output fixed at %s", gpio, fixed_out[i]);
				}
				if ( oeb_connections[i] == 0 && ! (i in fixed_oeb) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: missing user oeb for bidirection gpio", gpio);
				} else if ( IsHigh(oeb_sim[i]) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: bidirectional user oeb is always high - always input mode", gpio);
				} else if ( IsLow(oeb_sim[i]) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: bidirectional user oeb is always low - always output mode", gpio);
				} else {
					if ( ! IsLow(oeb_min[i]) ) {
						messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user oeb is never low - bidirectional output never enabled", gpio);
					}
					if ( ! IsHigh(oeb_max[i]) ) {
						messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user oeb is never high - bidirectional input never enabled", gpio);
					}
				}
				if ( analog_connections[i] ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: analog connection to user bidirectional gpio", gpio);
				}
				break;
			}
			case /USER_STD_ANALOG/: {
				if ( in_connections[i] > 0 ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user input connection to analog gpio - undriven", gpio);
				}
				if ( out_connections[i] == 0 && ! (i in fixed_out) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user mode requires output connection", gpio);
				} else if ( out_connections[i] > 0 && ! (i in fixed_out) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user output connection to analog gpio ignored", gpio);
				}
				if ( oeb_connections[i] == 0 && ! (i in fixed_oeb) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: user mode requires oeb connection", gpio);
				} else if ( oeb_connections[i] > 0 && ! (i in fixed_oeb) ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: user oeb connection to analog gpio ignored", gpio);
				}
				if ( analog_connections[i] == 0 ) {
					messages[length(messages)+1] = sprintf("GPIO %2d: Warning: missing analog connection to user analog gpio", gpio);
				}
				break;
			}
			case //: {
				printf "missing mode ";
				messages[length(messages)+1] = sprintf("GPIO %2d: ERROR: missing gpio configuration", gpio);
				break;
			}
			default: {
				printf "unknown mode";
				if ( analog_connections[i] > 0 ) {
					if ( in_connections[i] > 0 || (out_connections[i] > 0 && ! (i in fixed_out)) ) {  # both digital and analog
						printf "Warning: both analog and digital connections ";
					} else if ( ! IsHigh(oeb_sim[i]) ) {  # analog signals should disable output
						printf "Warning: oeb expected high for analog ";
					}
				} else if ( in_connections[i] > 0 && out_connections[i] == 0 ) {
					if ( ! IsHigh(oeb_sim[i]) ) {  # input only signals should never enable output
						printf "Warning: oeb expected high for input only ";
					}
				} else if ( out_connections[i] > 0 && ! (i in fixed_out) ) {
					if ( ! IsLow(oeb_min[i]) ) {  # output signals must be enable-able
						printf "ERROR: oeb must have possible low for output ";
					} else if ( in_connections[i] > 0 && IsLow(oeb_sim[i]) ) {  # input is always driven by user output in user mode
						printf "Warning: output always drives input. sky130: OK for pull up/down. ";
					}
				}
				break;
			}
			}
			new_errors = length(messages) - error_count;
			if ( new_errors > 0 ) {
				printf("%d warnings/errors", new_errors);
			}
			printf "\n";
		}
		if ( length(messages) == 0 ) {
			printf("\nNo warnings or errors detected\n");
		} else {
			printf("\n*** Detected the following warnings and/or errors: ***\n");
		}
		for ( line = 1; line <= length(messages); line++) {
			print messages[line];
		}
	}' $WORK_ROOT/user_defines.v $LOG_ROOT/cvc.oeb.log |
	tee $WORK_ROOT/cvc.oeb.report

if [[ $WORK_ROOT != $SIGNOFF_ROOT ]]; then
	cp $WORK_ROOT/cvc.oeb.report $SIGNOFF_ROOT/cvc.oeb.report
fi

if [[ $cvc_status -eq 0 && $(grep -c '^CVC: End:' $LOG_ROOT/cvc.oeb.log) -ne 1 ]]; then
	# CVC finished abnormally
	exit 5

elif [[ $cvc_status -eq 0 && $(grep -c ERROR $SIGNOFF_ROOT/cvc.oeb.report) -gt 0 ]]; then
	# CVC finished normally, but detected errors
	exit 6

elif [[ $cvc_status -eq 0 && $(grep -c Warning $SIGNOFF_ROOT/cvc.oeb.report) -gt 0 ]]; then
	# CVC finished normally, but detected warings
	exit 4

else
	exit $cvc_status
fi
