[syn] Add testsynthesis and sweep scripts for experiments

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/syn/tools/dc/at-plot.py b/hw/syn/tools/dc/at-plot.py
new file mode 100755
index 0000000..4b12697
--- /dev/null
+++ b/hw/syn/tools/dc/at-plot.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Note: this script relies on numpy and matplotlib, which both currently are
+# not standard python requirements of OpenTitan. If you wish to use this script,
+# make sure you install them with "pip3 install --user numpy matplotlib"
+
+import argparse
+import re
+import sys
+from pathlib import Path
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+USAGE = """
+This script parses reports generated with the sweep.tcl DC synthesis
+script and creates an AT-plot with that data.
+
+usage: ./at-plot.py ./REPORTS/<dut1_basename> ./REPORTS/<dut2_basename> ...
+
+In order to get convert area into gate equivalents (GE) the size of a GE
+can be supplied using the -g switch.
+
+The script currently supports plotting up to 10 different result series in
+one plot.
+"""
+
+
+def main():
+    parser = argparse.ArgumentParser(prog="")
+    parser.add_argument('result_series',
+                        type=str,
+                        nargs='+',
+                        help="Path and basename of result series.")
+
+    parser.add_argument(
+        '-g',
+        '--gate_equivalent',
+        type=float,
+        default=1.0,
+        help="Gate equivalent in square micrometre. Defaults to 1.0.")
+    parser.add_argument('-o',
+                        '--output',
+                        type=str,
+                        default="at-plot.png",
+                        help='Filename of at plot. Defaults to "at-plot.png"')
+    parser.add_argument('-t',
+                        '--title',
+                        type=str,
+                        default="AT Plot",
+                        help='Title of AT plot.')
+    parser.add_argument('--semilogy',
+                        action='store_true',
+                        help='semilogy plot.')
+
+    l = 0
+    labels = []
+
+    # line style and color setup
+    linestyles = ['--', '--', '--', '--', '--', '--', '--', '--', '--', '--']
+    markers = ['^', 'd', '.', '*', '>', '<', 'v', 's', 'h', 'o']
+    cmap = plt.get_cmap("Set1")
+
+    args = parser.parse_args()
+
+    if len(args.result_series) > 10:
+        print("Only up to ten result series can be plottet at once")
+        sys.exit(1)
+
+    print("Gate Equivalent is %.2f square micrometre" % args.gate_equivalent)
+
+    for basename in args.result_series:
+        report_filebase = Path(basename)
+
+        print("%s" % (report_filebase.name))
+
+        results = np.array([[]])
+
+        for rpt_area in report_filebase.parent.rglob(report_filebase.name +
+                                                     '_*_area.rpt'):
+            tmp_period = Path(rpt_area).name.split('_area')
+            tmp_period = tmp_period[0].split(report_filebase.name + '_')
+            row = np.array([[float(tmp_period[1]), 0.0, 0.0]])
+            if np.any(results):
+                results = np.append(results, row, axis=0)
+            else:
+                results = row
+            try:
+                with open(rpt_area, 'r') as f:
+                    full_file = f.read()
+                    tmp_area = re.findall(r"^Total cell area:.*",
+                                          full_file,
+                                          flags=re.MULTILINE)
+                    if tmp_area:
+                        tmp_area = tmp_area[0].split("Total cell area:")
+                        results[-1, 1] = float(
+                            tmp_area[1]) / args.gate_equivalent
+                    else:
+                        print("Error, could not find total cell area in %s" %
+                              (report_area))
+                        sys.exit(1)
+
+            except IOError as e:
+                print(str(e))
+                sys.exit(1)
+
+            try:
+                rpt_timing = rpt_area.parent.joinpath(
+                    rpt_area.name.replace('_area', '_timing'))
+                with open(rpt_timing, 'r') as f:
+                    full_file = f.read()
+                    tmp_slack = re.findall(r"^  slack \(.*",
+                                           full_file,
+                                           flags=re.MULTILINE)
+                    if tmp_slack:
+                        tmp_slack = tmp_slack[0].split(")")
+                        # adjust period with slack
+                        results[-1, 2] = results[-1][0] - float(tmp_slack[1])
+                    else:
+                        print("Error, could not find slack in %s" %
+                              (rpt_timing))
+                        sys.exit(1)
+
+            except IOError as e:
+                print(str(e))
+                sys.exit(1)
+
+        if np.any(results):
+
+            results = results[np.argsort(results[:, 0])]
+
+            if args.gate_equivalent == 1.0:
+                print("constraint [ns], achieved [ns], complexity [GE]")
+            else:
+                print("constraint [ns], achieved [ns], complexity [um^2]")
+
+            for k in range(len(results)):
+                print("%.2f, %.2f, %.2f" %
+                      (results[k][0], results[k][2], results[k][1]))
+            if args.semilogy:
+                plt.semilogy(results[:, 2],
+                             results[:, 1],
+                             color=cmap(l),
+                             linestyle=linestyles[l],
+                             marker=markers[l],
+                             linewidth=1.5,
+                             markersize=6,
+                             markeredgecolor='k')
+            else:
+                plt.plot(results[:, 2],
+                         results[:, 1],
+                         color=cmap(l),
+                         linestyle=linestyles[l],
+                         marker=markers[l],
+                         linewidth=1.5,
+                         markersize=6,
+                         markeredgecolor='k')
+
+            l += 1
+            labels += [report_filebase.name]
+
+    print("Parsed %d result series" % l)
+
+    plt.xlabel('Period [ns]')
+    if args.gate_equivalent == 1.0:
+        plt.ylabel('Complexity [um^2]')
+    else:
+        plt.ylabel('Complexity [GE]')
+    plt.grid(which='both')
+    plt.legend(labels)
+    plt.title(args.title)
+    plt.savefig(args.output)
+    plt.show()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/hw/syn/tools/dc/sweep.tcl b/hw/syn/tools/dc/sweep.tcl
new file mode 100644
index 0000000..fd8a9b3
--- /dev/null
+++ b/hw/syn/tools/dc/sweep.tcl
@@ -0,0 +1,115 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Simple tcl script for DC to do some wire-load-model-based sweep syntheses.
+
+#####################
+##  PREPARE FLOW   ##
+#####################
+
+source ../../../foundry/syn/dc/setup.tcl
+
+# paths
+set WORKLIB  "WORK"
+set REPDIR   "REPORTS"
+set DDCDIR   "DDC"
+set VLOGDIR  "NETLISTS"
+
+exec mkdir -p ${REPDIR} ${DDCDIR} ${VLOGDIR} ${WORKLIB}
+
+# define work lib path
+define_design_lib WORK -path $WORKLIB
+
+######################
+##   DESIGN SETUP  ##
+######################
+
+set DUT "prim_prince"
+# set DUT "prim_present"
+
+lappend search_path "../../../ip/prim/rtl/"
+set SRC {  "../../../ip/prim/rtl/prim_assert.sv" \
+           "../../../ip/prim/rtl/prim_present.sv" \
+           "../../../ip/prim/rtl/prim_prince.sv" \
+         }
+
+
+# additional defines
+set DEFINE ""
+
+# additional parameters
+set PARAMS ""
+
+set CLK_PIN clk_i
+set RST_PIN rst_ni
+
+set TCK_SWEEP { 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 }
+set IN_DEL  0.0
+set OUT_DEL 0.0
+
+###########################
+##         SWEEP         ##
+###########################
+
+foreach TCK $TCK_SWEEP {
+	###########################
+	##   ELABORATE DESIGN    ##
+	###########################
+
+	# delete previous designs.
+	remove_design -designs
+	sh rm -rf $WORKLIB/*
+
+	analyze -define ${DEFINE} -format sv ${SRC}   > "${REPDIR}/${DUT}_${TCK}_analyze.rpt"
+	elaborate  ${DUT} -parameters ${PARAMS}       > "${REPDIR}/${DUT}_${TCK}_elab.rpt"
+	link                                          > "${REPDIR}/${DUT}_${TCK}_link.rpt"
+	check_design                                  > "${REPDIR}/${DUT}_${TCK}_check.rpt"
+
+	write_file -format ddc -hierarchy -output "${DDCDIR}/${DUT}_${TCK}_elab.ddc"
+	write_file -format verilog -hierarchy -output "${DDCDIR}/${DUT}_${TCK}_elab.v"
+
+	###########################
+	##   APPLY CONSTRAINTS   ##
+	###########################
+
+	# timing constraint in ns
+	# set timing to 250 MHz
+	set DELAY   ${TCK}
+
+	create_clock ${CLK_PIN} -period ${TCK}
+
+	set_ideal_network ${CLK_PIN}
+	set_ideal_network ${RST_PIN}
+
+	set_max_delay ${DELAY} -from [all_inputs] -to [all_outputs]
+	set_input_delay ${IN_DEL} [remove_from_collection [all_inputs] {${CLK_PIN}}] -clock ${CLK_PIN}
+	set_output_delay ${OUT_DEL}  [all_outputs] -clock ${CLK_PIN}
+
+	set_driving_cell  -no_design_rule -lib_cell ${driving_cell} -pin X [all_inputs]
+	set_load [load_of ${load_lib}/${load_cell}/A] [all_outputs]
+
+	######################
+	##    MAP DESIGN    ##
+	######################
+
+	compile_ultra -gate_clock -scan  > "${REPDIR}/${DUT}_${TCK}_compile.rpt"
+
+	#################
+	##   REPORTS   ##
+	#################
+
+	report_timing -nosplit               > "${REPDIR}/${DUT}_${TCK}_timing.rpt"
+	report_area -hier -nosplit           > "${REPDIR}/${DUT}_${TCK}_area.rpt"
+	report_constraints -all_violators    > "${REPDIR}/${DUT}_${TCK}_constraints.rpt"
+
+	report_timing -nosplit -nworst 1000 -input -net -trans -cap > "${REPDIR}/${DUT}_${TCK}_timing_long.rpt"
+
+	#################
+	##   NETLIST   ##
+	#################
+
+	change_names -rules verilog -hierarchy
+	write_file -format ddc     -hierarchy -output "${DDCDIR}/${DUT}_${TCK}_mapped.ddc"
+	write_file -format verilog -hierarchy -output "${VLOGDIR}/${DUT}_${TCK}_mapped.v"
+}
diff --git a/hw/syn/tools/dc/testsynth.tcl b/hw/syn/tools/dc/testsynth.tcl
new file mode 100644
index 0000000..78ae6b6
--- /dev/null
+++ b/hw/syn/tools/dc/testsynth.tcl
@@ -0,0 +1,113 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Simple tcl script for DC to do some wire-load-model-based test syntheses.
+
+#####################
+##  PREPARE FLOW   ##
+#####################
+
+# tool setup
+source ../../../foundry/syn/dc/setup.tcl
+
+# paths
+set WORKLIB  "WORK"
+set REPDIR   "REPORTS"
+set DDCDIR   "DDC"
+set VLOGDIR  "NETLISTS"
+
+exec mkdir -p ${REPDIR} ${DDCDIR} ${VLOGDIR} ${WORKLIB}
+
+# define work lib path
+define_design_lib WORK -path $WORKLIB
+
+#######################
+##  DESIGN SOURCES  ###
+#######################
+
+set DUT "rv_plic_target"
+
+lappend search_path "../../../ip/prim/rtl/"
+set SRC {  "../../../ip/rv_plic/rtl/rv_plic_target.sv" }
+
+# additional defines
+set DEFINE ""
+
+# additional parameters
+set PARAMS "N_SOURCE=128"
+
+###########################
+##   ELABORATE DESIGN    ##
+###########################
+
+# delete previous designs.
+remove_design -designs
+sh rm -rf $WORKLIB/*
+
+analyze -define ${DEFINE} -format sv ${SRC}    > "${REPDIR}/${DUT}_analyze.rpt"
+elaborate  ${DUT} -parameters ${PARAMS}        > "${REPDIR}/${DUT}_elab.rpt"
+link                                           > "${REPDIR}/${DUT}_link.rpt"
+check_design                                   > "${REPDIR}/${DUT}_check.rpt"
+
+write_file -format ddc -hierarchy -output "${DDCDIR}/${DUT}_elab.ddc"
+write_file -format verilog -hierarchy -output "${DDCDIR}/${DUT}_elab.v"
+
+###########################
+##   APPLY CONSTRAINTS   ##
+###########################
+
+set CLK_PIN clk_i
+set RST_PIN rst_ni
+
+# timing constraint in ns
+# set timing to 250 MHz
+set TCK     4.0
+set IN_DEL  1.0
+set OUT_DEL 1.0
+set DELAY   ${TCK}
+
+create_clock ${CLK_PIN} -period ${TCK}
+
+set_ideal_network ${CLK_PIN}
+set_ideal_network ${RST_PIN}
+
+set_max_delay ${DELAY} -from [all_inputs] -to [all_outputs]
+set_input_delay ${IN_DEL} [remove_from_collection [all_inputs] {${CLK_PIN}}] -clock ${CLK_PIN}
+set_output_delay ${OUT_DEL}  [all_outputs] -clock ${CLK_PIN}
+
+set_driving_cell  -no_design_rule -lib_cell ${driving_cell} -pin X [all_inputs]
+set_load [load_of ${load_lib}/${load_cell}/A] [all_outputs]
+
+# set a nonzero critical range to be able to spot the violating paths better
+# in the report
+# set_critical_range 0.2 ${DUT}
+
+######################
+##    MAP DESIGN    ##
+######################
+
+compile_ultra -gate_clock -scan > "${REPDIR}/${DUT}_compile.rpt"
+
+# preserve hierarchy for reports
+#compile_ultra -gate_clock -scan -no_autoungroup > "${REPDIR}/${DUT}_compile.rpt"
+
+#################
+##   REPORTS   ##
+#################
+
+report_clocks                                 > "${REPDIR}/${DUT}_clocks.rpt"
+report_timing -nosplit -slack_lesser_than 0.0 > "${REPDIR}/${DUT}_timing.rpt"
+report_area   -hier -nosplit                  > "${REPDIR}/${DUT}_area.rpt"
+report_power  -hier -nosplit                  > "${REPDIR}/${DUT}_power.rpt"
+report_constraints -all_violators             > "${REPDIR}/${DUT}_constraints.rpt"
+
+report_timing      -nosplit -nworst 1000 -input -net -trans -cap > "${REPDIR}/${DUT}_timing_long.rpt"
+
+#################
+##   NETLIST   ##
+#################
+
+# change_names -rules verilog -hierarchy
+write_file -format ddc     -hierarchy -output "${DDCDIR}/${DUT}_mapped.ddc"
+write_file -format verilog -hierarchy -output "${VLOGDIR}/${DUT}_mapped.v"