Check for supported tool versions

Define supported tool versions in tool_requirements.py, and check them
in a fusesoc run. If an unsupported tool version is found, fusesoc
outputs an error like this:

```
$ fusesoc --cores-root ../../ run --target=lint lowrisc:ip:aes
INFO: Preparing lowrisc:constants:top_pkg:0
INFO: Preparing lowrisc:lint:comportable:0.1
...
INFO: Preparing lowrisc:tool:check_tool_requirements:0.1
INFO: Preparing lowrisc:lint:common:0.1
...
INFO: Preparing lowrisc:ip:aes:0.5

ERROR: verilator is too old: found version 4.016, need at least 4.028
ERROR: Tool requirements not fulfilled. Please update the tools and retry.
ERROR: Failed to build lowrisc:ip:aes:0.5 : pre_build script \
       'check_tool_requirements' exited with error code 1
```

The only version checked at this point is Verilator, which is set to
version 4.028, the first version to support wildcard matching for
`lint_off` rules.

The whole infrastructure has been created by @imphil in the Ibex
repository (see lowRISC/Ibex#604). This commit just copies the
framework over to OpenTitan.

Signed-off-by: Pirmin Vogel <vogelpi@lowrisc.org>
diff --git a/check_tool_requirements.core b/check_tool_requirements.core
new file mode 100644
index 0000000..375970c
--- /dev/null
+++ b/check_tool_requirements.core
@@ -0,0 +1,31 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:tool:check_tool_requirements:0.1"
+description: "Check tool requirements"
+
+filesets:
+  files_check_tool_requirements:
+    files:
+      - ./util/check_tool_requirements.py : { copyto: util/check_tool_requirements.py }
+      - ./tool_requirements.py : { copyto: tool_requirements.py }
+
+scripts:
+  check_tool_requirements:
+    cmd:
+      - python3
+      - util/check_tool_requirements.py
+    # TODO: Use this syntax once https://github.com/olofk/fusesoc/issues/353 is
+    # fixed. Remove the filesets from the default target, and also remove the
+    # copyto.
+    #filesets:
+    #  - files_check_tool_requirements
+
+targets:
+  default:
+    filesets:
+      - files_check_tool_requirements
+    hooks:
+      pre_build:
+        - tool_verilator ? (check_tool_requirements)
diff --git a/hw/lint/common.core b/hw/lint/common.core
index 64e323c..f7ce7f7 100644
--- a/hw/lint/common.core
+++ b/hw/lint/common.core
@@ -15,10 +15,13 @@
       - tools/ascentlint/common.waiver: {file_type: waiver}
       - tools/ascentlint/ascentlint-config.tcl: {file_type: tclSource}
 
+  files_check_tool_requirements:
+    depend:
+     - lowrisc:tool:check_tool_requirements
+
 targets:
   default: &default_target
     filesets:
       - tool_verilator  ? (files_verilator)
       - tool_ascentlint ? (files_ascentlint)
-
-
+      - files_check_tool_requirements
diff --git a/tool_requirements.py b/tool_requirements.py
new file mode 100644
index 0000000..7777c6e
--- /dev/null
+++ b/tool_requirements.py
@@ -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
+
+# Version requirements for various tools. Checked by tooling (e.g. fusesoc),
+# and inserted into the documentation.
+__TOOL_REQUIREMENTS__ = {
+    'verilator': '4.028',
+}
diff --git a/util/check_tool_requirements.py b/util/check_tool_requirements.py
new file mode 100755
index 0000000..5a59d4c
--- /dev/null
+++ b/util/check_tool_requirements.py
@@ -0,0 +1,63 @@
+#!/usr/bin/python3
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+from distutils.version import StrictVersion
+import logging as log
+import os
+import subprocess
+import sys
+
+# Display INFO log messages and up.
+log.basicConfig(level=log.INFO, format="%(levelname)s: %(message)s")
+
+# Populate __TOOL_REQUIREMENTS__
+topsrcdir = os.path.join(os.path.dirname(__file__), '..')
+exec(open(os.path.join(topsrcdir, 'tool_requirements.py')).read())
+
+def get_verilator_version():
+    try:
+        # Note: "verilator" needs to be called through a shell and with all
+        # arguments in a string, as it doesn't have a shebang, but instead
+        # relies on perl magic to parse command line arguments.
+        version_str = subprocess.run('verilator --version', shell=True,
+                                     check=True, stdout=subprocess.PIPE,
+                                     stderr=subprocess.STDOUT,
+                                     universal_newlines=True)
+        return version_str.stdout.split(' ')[1].strip()
+
+    except subprocess.CalledProcessError as e:
+        log.error("Unable to call Verilator to check version: " + str(e))
+        log.error(e.stdout)
+        return None
+
+def check_version(tool_name, required_version, actual_version):
+    if required_version is None or actual_version is None:
+        return False
+
+    if StrictVersion(actual_version) < StrictVersion(required_version):
+        log.error("%s is too old: found version %s, need at least %s",
+                  tool_name, actual_version, required_version)
+        return False
+    else:
+        log.info("Found sufficiently recent version of %s (found %s, need %s)",
+                 tool_name, actual_version, required_version)
+        return True
+
+
+def main():
+    any_failed = False
+
+    if not check_version('verilator', __TOOL_REQUIREMENTS__['verilator'],
+                         get_verilator_version()):
+        any_failed = True
+
+    if any_failed:
+        log.error("Tool requirements not fulfilled. "
+                  "Please update the tools and retry.")
+        return 1
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(main())