#! /bin/bash
#   run_hier_check: Checks layout hierarchy against verilog

#   Copyright 2022 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.

# Overview:
#  1. Extract verilog hierarchy, limit to TOP_CIRCUIT and child subcircuits.
#  2. Extract gds/oas hierachy, if necessary.
#  3. Compare
#

# Use case
# run_hier_check top_source verilog_files top_layout layout_file [primitive_prefix [gds_prefix]]
if [[ $# -lt 4 || $# -gt 6 ]]; then
	echo "usage: run_hier_check top_source verilog_files top_layout gds_file [primitive_prefix [gds_prefix]]"
	exit 1
fi

export TOP_SOURCE=$1
export SOURCE_FILES=$2
export TOP_LAYOUT=$3
export LAYOUT_FILE=$4
PRIMITIVE_PREFIX_FILTER='-e s/\<sky130_([^/_]*_)*_//'
if [[ $# -ge 5 ]]; then  # only if prefix is specified
	PRIMITIVE_PREFIX_FILTER="-e s/\\<$5//g"
fi
GDS_PREFIX_FILTER='-e s/^([A-Z0-9][A-Z0-9]_)*// -e s/\/([A-Z0-9][A-Z0-9]_)*/\//'
if [[ $# -eq 6 ]]; then  # only if prefix is specified
	GDS_PREFIX_FILTER="-e s/^($6)*// -e s/\/($6)*/\//"
fi

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

mkdir -p $LOG_ROOT
mkdir -p $SIGNOFF_ROOT
mkdir -p $WORK_ROOT
rm -f $LOG_ROOT/hier.log

log_file=$WORK_ROOT/hier.log
verilog_cell_file=$WORK_ROOT/verilog.cells
layout_cell_file=$WORK_ROOT/layout.cells

echo " "
echo "Running hierarchical comparison between verilog and layout..."

date "+BEGIN: %c" >$WORK_ROOT/hier.log
start_time=$SECONDS

if [[ $(echo $SOURCE_FILES | wc -w) -gt 0 ]]; then
	echo "Creating $verilog_cell_file from the following files..." |
		tee -a $WORK_ROOT/hier.log
	echo $SOURCE_FILES |
		sed -e 's/  */\n/g' |
		sed -e 's/^/	/' |
		tee -a $WORK_ROOT/hier.log

	sed '/^[ 	]*module[ 	].*[^ 	](/s/(/ (/' $(echo $SOURCE_FILES) |
		awk -v top=$TOP_SOURCE '
			$1 ~ /^\/\*/ { # skip block comments
				while ( ! /\*\// ) {
					getline;
				}
			}
			$1 == "module" {
				if ( $2 in modules ) {
					print "Duplicate module definition", $2 > "'$WORK_ROOT/hier.log'";
				}
				modules[$2] = 1;
				if ( ! ( $2 in cells ) ) {
					cells[$2] = 1;
				}
				module = $2;
				next;
			}
			$1 ~ /^[ 	]*\/\// { # skip single line comments
				next;
			}
			/ \(/ && $1 !~ /^\./ {
				key = module "/" $1;
				if ( ! ( $1 in cells ) ) {
					cells[$1] = 1;
				}
				if ( key in hier ) {
					hier[key] += 1;
				} else {
					subcells[module] = subcells[module] " " $1;
					hier[key] = 1;
				}
			}
			END {
				print_subckt[top] = 1;
				AddPrintSubckts(subcells[top]);
				for ( key in hier ) {
					split(key, path, "/");
					if ( path[1] in print_subckt ) {
						print key, hier[key];
					}
				}
				for ( cell in cells ) {
					print cell > "'$verilog_cell_file'";
				}
			}
			function AddPrintSubckts(subckt_list, subckts, subckt_it) {
				subcell_count = split(subckt_list, subckts);
				for ( subckt_it in subckts ) {
					print_subckt[subckts[subckt_it]] = 1;
					AddPrintSubckts(subcells[subckts[subckt_it]]);
				}
			}' - |
		grep -v // |
		sed -E $PRIMITIVE_PREFIX_FILTER |
		sort >$WORK_ROOT/verilog.hier
else
	echo "No verilog files..."
	rm -f $WORK_ROOT/verilog.hier $verilog_cell_file
	touch $WORK_ROOT/verilog.hier
	cat >$verilog_cell_file <<-EOF
		$TOP_SOURCE
	EOF
fi

if [[ $LAYOUT_FILE ]]; then
	if [[ $LAYOUT_FILE == *.gz ]]; then
		CAT=zcat
		BASE_LAYOUT=${LAYOUT_FILE%.gz}
	else
		CAT=cat
		BASE_LAYOUT=$LAYOUT_FILE
	fi
	EXT=${BASE_LAYOUT##*.}
	if [[ "$EXT" == "txt" ]]; then
		TEXT_FILE=$BASE_LAYOUT
	elif [[ "$EXT" == "gds" || "$EXT" == "oas" ]]; then
		TEXT_FILE=$WORK_ROOT/layout.txt
		cat >$WORK_ROOT/gds2txt.py <<-EOF
			import pya

			app = pya.Application.instance()
			opt = pya.SaveLayoutOptions()
			layout_view = pya.Layout()

			input_layout = "$LAYOUT_FILE"
			output = "$TEXT_FILE"
			# Setting the name of the output file and setting the substitution character
			print("[INFO] Changing from " + input_layout + "\n	to " + output)
			opt.set_format_from_filename(output)
			opt.oasis_substitution_char=''

			# Reading the input file and writing it to the output file name
			layout_view.read(input_layout)
			for cell_it in layout_view.each_cell():
			    if cell_it.name.endswith("$TOP_LAYOUT"):
			       myIndex = layout_view.cell(cell_it.name).cell_index()
			       break
			opt.select_cell(myIndex)
			opt.add_layer(0, pya.LayerInfo())
			layout_view.write(output, opt)

			app.exit(0)
		EOF
		klayout -b -rm $WORK_ROOT/gds2txt.py |
			tee -a $WORK_ROOT/hier.log
		gzip -f $TEXT_FILE
		CAT=zcat
	fi

	$CAT $TEXT_FILE |
	awk '
		/^STRNAME/ {
			module = $2;
		}
		/^SNAME/ {
			key = module "/" $2;
			if ( key in hier ) {
				hier[key] += 1;
			} else {
				hier[key] = 1;
			}
		}
		END {
			for ( key in hier ) {
				print key, hier[key];
			}
		}' - |
		sed -E $GDS_PREFIX_FILTER $PRIMITIVE_PREFIX_FILTER |
		sort -u >$WORK_ROOT/layout.hier
	$CAT $TEXT_FILE |
		grep STRNAME |
		awk '{print $2}' |
		sort >$WORK_ROOT/layout.cells

elif [[ -f $WORK_ROOT/layout.hier ]]; then
	echo "Reusing $WORK_ROOT/layout.hier"
else
	echo "Could not locate $WORK_ROOT/layout.hier"
	exit 1
fi

comm -23 $WORK_ROOT/verilog.hier $WORK_ROOT/layout.hier |
	awk '{print $1}' |
	fgrep -f - $WORK_ROOT/verilog.hier $WORK_ROOT/layout.hier |
	sed 's/:/ /' |
	awk '	BEGIN {
			print "verilog,,layout";
		}
		/verilog.hier/ {
			verilog_count += 1;
			verilog[verilog_count] = $2;
			instances[$2] = $3;
		}
		/layout.hier/ {
			if ( match_count == verilog_count || verilog[match_count+1] > $2 ) {
				print ",," $2 "," $3;
			} else {
				while ( match_count < verilog_count && verilog[match_count+1] <= $2 ) {
					match_count += 1;
					if ( verilog[match_count] < $2 ) {
						print verilog[match_count] "," instances[verilog[match_count]];
					} else {  # equal
						print verilog[match_count] "," instances[verilog[match_count]] "," $2 "," $3;
					}
				}
				if ( match_count == verilog_count && verilog[match_count] != $2 ) {
					print verilog[match_count] "," instances[verilog[match_count]];
				}
			}
		}
		END {
			while ( match_count < verilog_count ) {
				match_count += 1;
				print verilog[match_count] "," instances[verilog[match_count]];
			}
		}' - >$WORK_ROOT/hier.csv

if [[ $(cat $WORK_ROOT/hier.csv | wc -l) -eq 1 ]]; then
	echo "Hierarchy check for $TOP_SOURCE passed." |
		tee -a $WORK_ROOT/hier.log
else
	echo "Hierarchy check for $TOP_SOURCE failed. See $WORK_ROOT/hier.csv" |
		tee -a $WORK_ROOT/hier.log
fi

date "+END: %c" >>$WORK_ROOT/hier.log
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/hier.log
if [[ $WORK_ROOT != $LOG_ROOT ]]; then
	cp $WORK_ROOT/hier.log $LOG_ROOT/.
fi
if [[ $WORK_ROOT != $SIGNOFF_ROOT ]]; then
	cp $WORK_ROOT/hier.csv $SIGNOFF_ROOT/.
fi
