[dv] Add simulation result summary page

Here is the report that will be generated
https://reports.opentitan.org/hw/top_earlgrey/dv/summarys.html
Also fix xbar coverage error

Signed-off-by: Weicai Yang <weicai@google.com>
diff --git a/util/dvsim.py b/util/dvsim.py
index ee516a7..258d93b 100755
--- a/util/dvsim.py
+++ b/util/dvsim.py
@@ -458,10 +458,11 @@
         cfg.deploy_objects()
 
         # Generate results.
-        results = cfg.gen_results()
+        cfg.gen_results()
 
         # Publish results
         if args.publish: cfg.publish_results()
+
     else:
         log.info("No items specified to be run.")
 
diff --git a/util/dvsim/FlowCfg.py b/util/dvsim/FlowCfg.py
index ad02c06..53838f7 100644
--- a/util/dvsim/FlowCfg.py
+++ b/util/dvsim/FlowCfg.py
@@ -75,6 +75,8 @@
 
         # Full results in md text.
         self.results_md = ""
+        # Summary results in md text.
+        self.results_summary_md = ""
 
     def __post_init__(self):
         # Run some post init checks
@@ -343,7 +345,51 @@
             result = item._gen_results()
             print(result)
             results.append(result)
-        return results
+
+        if self.is_master_cfg: self.gen_results_summary()
+
+    def gen_results_summary(self):
+        '''Public facing API to generate summary results for each IP/cfg file
+        '''
+        return
+
+    def publish_results_summary(self):
+        '''Public facing API for publishing md format results to the opentitan web server.
+        '''
+        results_html_file = 'summary.html'
+        # master cfg doesn't have server info, instead, get it from cfgs[0]
+        path = self.cfgs[0].results_server_prefix + self.cfgs[0].results_server + '/' + \
+               self.rel_path
+        results_page = path + '/' + results_html_file
+        results_page_url = results_page.replace(
+            self.cfgs[0].results_server_prefix,
+            self.cfgs[0].results_server_url_prefix)
+
+        # Assume that a 'style.css' is available at root path
+        css_path = (
+            (len(self.rel_path.split("/")) + 2) * "../") + "css/style.css"
+
+        # Publish the results page.
+        # First, write the results html file temporarily to the scratch area.
+        f = open(results_html_file, 'w')
+        f.write(
+            md_results_to_html(self.results_title, css_path,
+                               self.results_summary_md))
+        f.close()
+        rm_cmd = "rm -rf " + results_html_file + "; "
+
+        log.info("Publishing results summary to %s", results_page_url)
+        cmd = self.cfgs[0].results_server_cmd + " cp " + results_html_file + " " + \
+              results_page + "; " + rm_cmd
+        log.log(VERBOSE, cmd)
+        try:
+            cmd_output = subprocess.run(args=cmd,
+                                        shell=True,
+                                        stdout=subprocess.PIPE,
+                                        stderr=subprocess.STDOUT)
+            log.log(VERBOSE, cmd_output.stdout.decode("utf-8"))
+        except Exception as e:
+            log.error("%s: Failed to publish results:\n\"%s\"", e, str(cmd))
 
     def _publish_results(self):
         '''Publish results to the opentitan web server.
@@ -492,3 +538,5 @@
         '''
         for item in self.cfgs:
             item._publish_results()
+
+        if self.is_master_cfg: self.publish_results_summary()
diff --git a/util/dvsim/SimCfg.py b/util/dvsim/SimCfg.py
index 20aa1a4..93c8401 100644
--- a/util/dvsim/SimCfg.py
+++ b/util/dvsim/SimCfg.py
@@ -104,6 +104,7 @@
         # Parse the cfg_file file tree
         self.parse_flow_cfg(flow_cfg_file)
 
+        self.final_total = []
         # Stop here if this is a master cfg list
         if self.is_master_cfg: return
 
@@ -463,6 +464,9 @@
                 regr_results=regr_results,
                 map_full_testplan=self.map_full_testplan)
             results_str += "\n"
+            self.final_total = self.testplan.final_total
+            # append link
+            self.final_total.append(self.append_result_link("Link"))
 
         # Append coverage results of coverage was enabled.
         if self.cov and self.cov_report_deploy.status == "P":
@@ -483,6 +487,27 @@
         # Return only the tables
         return results_str
 
+    def gen_results_summary(self):
+
+        # sim summary result has 4 columns provided by Testplan::results_table
+        table = [["Name", "Passing", "Total", "Pass Rate", "Detail"]]
+        colalign = ("center", ) * 5
+        for item in self.cfgs:
+            table.append(item.final_total)
+        self.results_summary_md = "## Simulation Summary Results\n"
+        self.results_summary_md += tabulate(table,
+                                            headers="firstrow",
+                                            tablefmt="pipe",
+                                            colalign=colalign)
+        print(self.results_summary_md)
+        return self.results_summary_md
+
+    def append_result_link(self, link_name):
+        results_page = self.results_server_dir + '/' + 'results.html'
+        results_page_url = results_page.replace(self.results_server_prefix,
+                                                self.results_server_url_prefix)
+        return "[%s](%s)" % (link_name, results_page_url)
+
     def _cov_analyze(self):
         '''Use the last regression coverage data to open up the GUI tool to
         analyze the coverage.
diff --git a/util/testplanner/class_defs.py b/util/testplanner/class_defs.py
index 05694d9..56b4177 100644
--- a/util/testplanner/class_defs.py
+++ b/util/testplanner/class_defs.py
@@ -162,6 +162,9 @@
     def __init__(self, name):
         self.name = name
         self.entries = []
+        self.final_total = []
+        self.results = ""
+
         if name == "":
             print("Error: testplan name cannot be empty")
             sys.exit(1)
@@ -324,7 +327,13 @@
                 ])
                 milestone = ""
                 entry_name = ""
-        return tabulate(table,
-                        headers="firstrow",
-                        tablefmt="pipe",
-                        colalign=colalign)
+                if entry.milestone == "N.A." and entry.name == "N.A.":
+                    self.final_total = [
+                        self.name.upper(), test["passing"], test["total"],
+                        pass_rate
+                    ]
+        self.results = tabulate(table,
+                                headers="firstrow",
+                                tablefmt="pipe",
+                                colalign=colalign)
+        return self.results
diff --git a/util/tlgen/generate.py b/util/tlgen/generate.py
index 6320783..3b39ab0 100644
--- a/util/tlgen/generate.py
+++ b/util/tlgen/generate.py
@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: Apache-2.0
 
 import logging as log
+
 from mako import exceptions
 from mako.template import Template
 from pkg_resources import resource_filename
@@ -11,7 +12,6 @@
 from .xbar import Xbar
 
 
-
 def generate(xbar):  #xbar: Xbar -> str
     """generate uses elaborated model then creates top level Xbar module
     with prefix.
diff --git a/util/tlgen/generate_tb.py b/util/tlgen/generate_tb.py
index 446f5f4..64b0d6b 100644
--- a/util/tlgen/generate_tb.py
+++ b/util/tlgen/generate_tb.py
@@ -16,7 +16,7 @@
     # list all the generate files for TB
     tb_files = [
         "xbar_env_pkg__params.sv", "tb__xbar_connect.sv", "xbar.sim.core",
-        "xbar.bind.sv", "Makefile", "xbar.sim_cfg.hjson"
+        "xbar.bind.sv", "Makefile", "xbar.sim_cfg.hjson", "xbar.testplan.hjson"
     ]
 
     for fname in tb_files:
@@ -29,8 +29,15 @@
             fname = "xbar_%s_bind.sv" % (xbar.name)
         elif fname == "xbar.sim_cfg.hjson":
             fname = "xbar_%s_sim_cfg.hjson" % (xbar.name)
+        elif fname == "xbar.testplan.hjson":
+            fname = "xbar_%s_testplan.hjson" % (xbar.name)
 
-        dv_filepath = dv_path / fname
+        # save testplan at data directory
+        if fname == "xbar_%s_testplan.hjson" % (xbar.name):
+            dv_filepath = dv_path / '../../data/autogen' / fname
+        else:
+            dv_filepath = dv_path / fname
+
         with dv_filepath.open(mode='w', encoding='UTF-8') as fout:
             try:
                 fout.write(tpl.render(xbar=xbar))
diff --git a/util/tlgen/xbar.sim_cfg.hjson.tpl b/util/tlgen/xbar.sim_cfg.hjson.tpl
index decb93e..4ff37b2 100644
--- a/util/tlgen/xbar.sim_cfg.hjson.tpl
+++ b/util/tlgen/xbar.sim_cfg.hjson.tpl
@@ -7,12 +7,6 @@
   // Top level dut name (sv module).
   dut: xbar_${xbar.name}
 
-  // Testplan hjson file.
-  testplan: "{proj_root}/hw/top_earlgrey/ip/xbar_${xbar.name}/data/autogen/xbar_${xbar.name}_testplan.hjson"
-
-  // Add xbar_${xbar.name} specific exclusion files.
-  vcs_cov_excl_files: ["{proj_root}/hw/top_earlgrey/ip/xbar_${xbar.name}/dv/cov/xbar_cov_excl.el"]
-
   // Import additional common sim cfg files.
   import_cfgs: [// xbar common sim cfg file
                 "{proj_root}/hw/ip/tlul/generic_dv/xbar_sim_cfg.hjson"]
diff --git a/util/tlgen/xbar.testplan.hjson.tpl b/util/tlgen/xbar.testplan.hjson.tpl
new file mode 100644
index 0000000..11e5cc8
--- /dev/null
+++ b/util/tlgen/xbar.testplan.hjson.tpl
@@ -0,0 +1,9 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// xbar_${xbar.name}_testplan.hjson file generated by `tlgen.py` tool
+{
+  name: "xbar_${xbar.name}"
+  import_testplans: ["hw/ip/tlul/data/tlul_testplan.hjson"]
+}