|  | # 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 |