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