Michael Schaffner | b5a88f2 | 2019-11-26 19:43:37 -0800 | [diff] [blame] | 1 | # Copyright lowRISC contributors. |
| 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | r"""Helper functions for parsing a systemverilog module header. |
| 5 | """ |
| 6 | |
| 7 | from io import StringIO |
| 8 | from pathlib import Path |
| 9 | |
| 10 | |
| 11 | class Param(): |
| 12 | name = "" |
| 13 | datatype = "" |
| 14 | style = "" |
| 15 | value = "" |
| 16 | |
| 17 | def __init__(self, name_="", datatype_="", style_="", value_=""): |
| 18 | self.name = name_ |
| 19 | self.datatype = datatype_ |
| 20 | self.style = style_ |
| 21 | self.value = value_ |
| 22 | |
| 23 | |
| 24 | class Port(): |
| 25 | name = "" |
| 26 | datatype = "" |
| 27 | direction = "" |
| 28 | |
| 29 | def __init__(self, name_="", datatype_="", direction_=""): |
| 30 | self.name = name_ |
| 31 | self.datatype = datatype_ |
| 32 | self.direction = direction_ |
| 33 | |
| 34 | |
| 35 | class Dut(): |
| 36 | name = "" |
| 37 | pkgs = [] |
| 38 | params = [] |
| 39 | ports = [] |
| 40 | deps = [] |
| 41 | is_cip = False |
| 42 | |
| 43 | def __init__(self, name_="", pkgs_=[], ports_=[], \ |
| 44 | params_=[], deps_=[], is_cip_=False): |
| 45 | self.name = name_ |
| 46 | self.pkgs = pkgs_ |
| 47 | self.ports = ports_ |
| 48 | self.params = params_ |
| 49 | self.deps = deps_ |
| 50 | self.is_cip = is_cip_ |
| 51 | |
Michael Schaffner | ccef827 | 2019-12-06 08:54:55 -0800 | [diff] [blame] | 52 | # filters out localparams |
| 53 | def get_param_style(self, style): |
| 54 | params = [] |
| 55 | for p in self.params: |
| 56 | params += [p] if p.style == style else [] |
| 57 | return params |
Michael Schaffner | b5a88f2 | 2019-11-26 19:43:37 -0800 | [diff] [blame] | 58 | |
| 59 | # strip // comments |
| 60 | def strip_comments(buf): |
| 61 | outbuf = "" |
| 62 | for line in buf.split('\n'): |
| 63 | for k in range(len(line) - 1): |
| 64 | if line[k:k + 2] == "//": |
| 65 | break |
| 66 | else: |
| 67 | outbuf += line[k] |
| 68 | else: |
| 69 | if line: |
| 70 | outbuf += line[-1] |
| 71 | outbuf += " " |
| 72 | |
| 73 | return outbuf |
| 74 | |
| 75 | |
| 76 | PARENTH_STYLES = {'(': ')', '[': ']', '{': '}'} |
| 77 | |
| 78 | |
| 79 | # parse parenthesis and optionally handle the body using the handler function |
| 80 | # if no handler is specified, it just calls itself recursively |
| 81 | def parse_parenthesis(hdl_raw, dut, style='(', handler=None): |
| 82 | if style not in PARENTH_STYLES: |
| 83 | print("Unknown parenthesis style %s, aborting." % style) |
| 84 | else: |
| 85 | par_opened = False |
| 86 | while hdl_raw: |
| 87 | c = hdl_raw.pop(0) |
| 88 | if c == style: |
| 89 | par_opened = True |
| 90 | if handler: |
| 91 | handler(hdl_raw, dut) |
| 92 | else: |
| 93 | parse_parenthesis(hdl_raw, dut, style) |
| 94 | if c == PARENTH_STYLES[style]: |
| 95 | if not par_opened: |
| 96 | hdl_raw.insert(0, c) |
| 97 | break |
| 98 | return |
| 99 | |
| 100 | |
| 101 | # parse individual port declarations |
| 102 | # may not be fully correct with unpacked dimensions, |
| 103 | # but works for the common case |
| 104 | def parse_port(buf, dut): |
| 105 | words = buf.split() |
| 106 | if words: |
| 107 | if words[0] not in ["input", "inout", "output"]: |
| 108 | print("Warning, expected input, inout or output keyword") |
| 109 | else: |
| 110 | if len(words) > 2: |
| 111 | dut.ports += [Port(words[-1], "".join(words[1:-1]), words[0])] |
| 112 | elif len(words) == 2: |
| 113 | dut.ports += [Port(words[-1], "", words[0])] |
| 114 | else: |
| 115 | print("Warning, port declaration incomplete") |
| 116 | else: |
| 117 | print("Warning, port declaration empty") |
| 118 | return |
| 119 | |
| 120 | |
| 121 | # parse individual parameter declaration |
| 122 | def parse_param(buf, dut): |
| 123 | words = buf.split('=') |
| 124 | value = '='.join(words[1:]) |
| 125 | words = words[0].split() |
| 126 | |
| 127 | if words: |
| 128 | if words[0] not in ["parameter", "localparam"]: |
| 129 | print("Warning, expected parameter or localparam keyword") |
| 130 | else: |
| 131 | if len(words) > 2: |
| 132 | dut.params += [ |
| 133 | Param(words[-1], " ".join(words[1:-1]), words[0], value) |
| 134 | ] |
| 135 | elif len(words) == 2: |
| 136 | dut.params += [Param(words[-1], "", words[0], value)] |
| 137 | else: |
| 138 | print("Warning, parameter declaration incomplete") |
| 139 | else: |
| 140 | print("Warning, port declaration empty") |
| 141 | return |
| 142 | |
| 143 | |
| 144 | # extract individual declarations |
| 145 | def parse_declaration(hdl_raw, dut, handler): |
| 146 | buf = "" |
| 147 | par_opened = 0 |
| 148 | while hdl_raw: |
| 149 | c = hdl_raw.pop(0) |
| 150 | # end of this port |
| 151 | if c == ',': |
| 152 | handler(buf, dut) |
| 153 | buf = "" |
| 154 | elif c == '(': |
| 155 | par_opened = par_opened + 1 |
| 156 | buf += c |
| 157 | elif c == ')': |
| 158 | if par_opened: |
| 159 | # part of the declaration |
| 160 | par_opened = par_opened - 1 |
| 161 | buf += c |
| 162 | else: |
| 163 | # end of the declaration list |
| 164 | handler(buf, dut) |
| 165 | hdl_raw.insert(0, ')') |
| 166 | break |
| 167 | else: |
| 168 | buf += c |
| 169 | return |
| 170 | |
| 171 | |
| 172 | def parse_ports(hdl_raw, dut): |
| 173 | parse_declaration(hdl_raw, dut, parse_port) |
| 174 | |
| 175 | |
| 176 | def parse_params(hdl_raw, dut): |
| 177 | parse_declaration(hdl_raw, dut, parse_param) |
| 178 | |
| 179 | |
| 180 | def parse_module(words, dut): |
| 181 | # check for imports first |
| 182 | while words: |
| 183 | w = words.pop(0) |
| 184 | if w == "import": |
| 185 | if words: |
| 186 | # get package names to import |
| 187 | pkg = words.pop(0).split(";") |
| 188 | dut.pkgs += [pkg[0]] |
| 189 | else: |
| 190 | print("Unexpected end") |
| 191 | # stop package scan and move on to body |
| 192 | elif '#' in w or '(' in w: |
| 193 | words.insert(0, w) |
| 194 | break |
| 195 | |
| 196 | hdl_raw = list(' '.join(words)) |
| 197 | while hdl_raw: |
| 198 | c = hdl_raw.pop(0) |
| 199 | if c == '#': |
| 200 | parse_parenthesis(hdl_raw, dut, '(', parse_params) |
| 201 | elif c == '(': |
| 202 | hdl_raw.insert(0, '(') |
| 203 | parse_parenthesis(hdl_raw, dut, '(', parse_ports) |
| 204 | elif c == ';': |
| 205 | break |
| 206 | return |
| 207 | |
| 208 | |
| 209 | # simplistic module declaration parser. |
| 210 | # this works in most cases, but there are exceptions. |
| 211 | def parse_file(file): |
| 212 | dut = Dut() |
| 213 | hdl_raw = "" |
| 214 | with open(file, 'r') as fp: |
| 215 | hdl_raw = strip_comments(fp.read()) |
| 216 | # extract first module declaration in file and extract port list |
| 217 | # also look for imported packages (either in the module declaration |
| 218 | # or before it) |
| 219 | words = hdl_raw.split() |
| 220 | while words: |
| 221 | w = words.pop(0) |
| 222 | if w == "import": |
| 223 | if words: |
| 224 | # get package names to import |
| 225 | pkg = words.pop(0).split(";") |
| 226 | dut.pkgs += [pkg[0]] |
| 227 | else: |
| 228 | print("Unexpected end") |
| 229 | elif w == "module": |
| 230 | if words: |
| 231 | # get module name |
| 232 | dut.name = words[0] |
| 233 | # parse the module params and port list and exit |
| 234 | parse_module(words, dut) |
| 235 | break |
| 236 | return dut |