[fpvgen] FPV boilerplate generator script
This adds a boilerplate generator script for FPV testbenches. The
generator can be used for both comportable or noncomportable IPs.
Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/util/fpvgen/sv_parse.py b/util/fpvgen/sv_parse.py
new file mode 100644
index 0000000..325e798
--- /dev/null
+++ b/util/fpvgen/sv_parse.py
@@ -0,0 +1,231 @@
+#!/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
+r"""Helper functions for parsing a systemverilog module header.
+"""
+
+from io import StringIO
+from pathlib import Path
+
+
+class Param():
+ name = ""
+ datatype = ""
+ style = ""
+ value = ""
+
+ def __init__(self, name_="", datatype_="", style_="", value_=""):
+ self.name = name_
+ self.datatype = datatype_
+ self.style = style_
+ self.value = value_
+
+
+class Port():
+ name = ""
+ datatype = ""
+ direction = ""
+
+ def __init__(self, name_="", datatype_="", direction_=""):
+ self.name = name_
+ self.datatype = datatype_
+ self.direction = direction_
+
+
+class Dut():
+ name = ""
+ pkgs = []
+ params = []
+ ports = []
+ deps = []
+ is_cip = False
+
+ def __init__(self, name_="", pkgs_=[], ports_=[], \
+ params_=[], deps_=[], is_cip_=False):
+ self.name = name_
+ self.pkgs = pkgs_
+ self.ports = ports_
+ self.params = params_
+ self.deps = deps_
+ self.is_cip = is_cip_
+
+
+# strip // comments
+def strip_comments(buf):
+ outbuf = ""
+ for line in buf.split('\n'):
+ for k in range(len(line) - 1):
+ if line[k:k + 2] == "//":
+ break
+ else:
+ outbuf += line[k]
+ else:
+ if line:
+ outbuf += line[-1]
+ outbuf += " "
+
+ return outbuf
+
+
+PARENTH_STYLES = {'(': ')', '[': ']', '{': '}'}
+
+
+# parse parenthesis and optionally handle the body using the handler function
+# if no handler is specified, it just calls itself recursively
+def parse_parenthesis(hdl_raw, dut, style='(', handler=None):
+ if style not in PARENTH_STYLES:
+ print("Unknown parenthesis style %s, aborting." % style)
+ else:
+ par_opened = False
+ while hdl_raw:
+ c = hdl_raw.pop(0)
+ if c == style:
+ par_opened = True
+ if handler:
+ handler(hdl_raw, dut)
+ else:
+ parse_parenthesis(hdl_raw, dut, style)
+ if c == PARENTH_STYLES[style]:
+ if not par_opened:
+ hdl_raw.insert(0, c)
+ break
+ return
+
+
+# parse individual port declarations
+# may not be fully correct with unpacked dimensions,
+# but works for the common case
+def parse_port(buf, dut):
+ words = buf.split()
+ if words:
+ if words[0] not in ["input", "inout", "output"]:
+ print("Warning, expected input, inout or output keyword")
+ else:
+ if len(words) > 2:
+ dut.ports += [Port(words[-1], "".join(words[1:-1]), words[0])]
+ elif len(words) == 2:
+ dut.ports += [Port(words[-1], "", words[0])]
+ else:
+ print("Warning, port declaration incomplete")
+ else:
+ print("Warning, port declaration empty")
+ return
+
+
+# parse individual parameter declaration
+def parse_param(buf, dut):
+ words = buf.split('=')
+ value = '='.join(words[1:])
+ words = words[0].split()
+
+ if words:
+ if words[0] not in ["parameter", "localparam"]:
+ print("Warning, expected parameter or localparam keyword")
+ else:
+ if len(words) > 2:
+ dut.params += [
+ Param(words[-1], " ".join(words[1:-1]), words[0], value)
+ ]
+ elif len(words) == 2:
+ dut.params += [Param(words[-1], "", words[0], value)]
+ else:
+ print("Warning, parameter declaration incomplete")
+ else:
+ print("Warning, port declaration empty")
+ return
+
+
+# extract individual declarations
+def parse_declaration(hdl_raw, dut, handler):
+ buf = ""
+ par_opened = 0
+ while hdl_raw:
+ c = hdl_raw.pop(0)
+ # end of this port
+ if c == ',':
+ handler(buf, dut)
+ buf = ""
+ elif c == '(':
+ par_opened = par_opened + 1
+ buf += c
+ elif c == ')':
+ if par_opened:
+ # part of the declaration
+ par_opened = par_opened - 1
+ buf += c
+ else:
+ # end of the declaration list
+ handler(buf, dut)
+ hdl_raw.insert(0, ')')
+ break
+ else:
+ buf += c
+ return
+
+
+def parse_ports(hdl_raw, dut):
+ parse_declaration(hdl_raw, dut, parse_port)
+
+
+def parse_params(hdl_raw, dut):
+ parse_declaration(hdl_raw, dut, parse_param)
+
+
+def parse_module(words, dut):
+ # check for imports first
+ while words:
+ w = words.pop(0)
+ if w == "import":
+ if words:
+ # get package names to import
+ pkg = words.pop(0).split(";")
+ dut.pkgs += [pkg[0]]
+ else:
+ print("Unexpected end")
+ # stop package scan and move on to body
+ elif '#' in w or '(' in w:
+ words.insert(0, w)
+ break
+
+ hdl_raw = list(' '.join(words))
+ while hdl_raw:
+ c = hdl_raw.pop(0)
+ if c == '#':
+ parse_parenthesis(hdl_raw, dut, '(', parse_params)
+ elif c == '(':
+ hdl_raw.insert(0, '(')
+ parse_parenthesis(hdl_raw, dut, '(', parse_ports)
+ elif c == ';':
+ break
+ return
+
+
+# simplistic module declaration parser.
+# this works in most cases, but there are exceptions.
+def parse_file(file):
+ dut = Dut()
+ hdl_raw = ""
+ with open(file, 'r') as fp:
+ hdl_raw = strip_comments(fp.read())
+ # extract first module declaration in file and extract port list
+ # also look for imported packages (either in the module declaration
+ # or before it)
+ words = hdl_raw.split()
+ while words:
+ w = words.pop(0)
+ if w == "import":
+ if words:
+ # get package names to import
+ pkg = words.pop(0).split(";")
+ dut.pkgs += [pkg[0]]
+ else:
+ print("Unexpected end")
+ elif w == "module":
+ if words:
+ # get module name
+ dut.name = words[0]
+ # parse the module params and port list and exit
+ parse_module(words, dut)
+ break
+ return dut