| # 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_ | 
 |  | 
 |     # filters out localparams | 
 |     def get_param_style(self, style): | 
 |       params = [] | 
 |       for p in self.params: | 
 |         params += [p] if p.style == style else [] | 
 |       return params | 
 |  | 
 | # 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 |