xref: /XiangShan/scripts/coverage/statistics.py (revision c8b1e4db9cf506f40d3cbddfbd259cfecf0168b7)
1#/usr/bin/python3
2# -*- coding: UTF-8 -*-
3import sys
4import re
5import copy
6import pprint
7
8LINE_COVERRED = "LINE_COVERRED"
9NOT_LINE_COVERRED = "NOT_LINE_COVERRED"
10TOGGLE_COVERRED = "TOGGLE_COVERRED"
11NOT_TOGGLE_COVERRED = "NOT_TOGGLE_COVERRED"
12DONTCARE = "DONTCARE"
13
14BEGIN = "BEGIN"
15END = "END"
16CHILDREN = "CHILDREN"
17MODULE = "MODULE"
18INSTANCE = "INSTANCE"
19TYPE = "TYPE"
20ROOT = "ROOT"
21NODE = "NODE"
22SELFCOVERAGE = "SELFCOVERAGE"
23TREECOVERAGE = "TREECOVERAGE"
24LINECOVERAGE = 0
25TOGGLECOVERAGE = 1
26
27def check_one_hot(l):
28    cnt = 0
29    for e in l:
30        if e:
31            cnt += 1
32    return cnt <= 1
33
34def get_lines(input_file):
35    lines = []
36    with open(input_file) as f:
37        for line in f:
38            lines.append(line)
39    return lines
40
41def get_line_annotation(lines):
42    line_annotations = []
43    # pattern_1: 040192     if(array_0_MPORT_en & array_0_MPORT_mask) begin
44    # pattern_2: 2218110        end else if (_T_30) begin // @[Conditional.scala 40:58]
45    # pattern_2: 000417     end else begin
46    line_coverred_pattern_1 = re.compile('^\s*(\d+)\s+if')
47    line_coverred_pattern_2 = re.compile('^\s*(\d+)\s+end else')
48    not_line_coverred_pattern_1 = re.compile('^\s*(%0+)\s+if')
49    not_line_coverred_pattern_2 = re.compile('^\s*(%0+)\s+end else')
50
51    toggle_coverred_pattern_1 = re.compile('^\s*(\d+)\s+reg')
52    toggle_coverred_pattern_2 = re.compile('^\s*(\d+)\s+wire')
53    toggle_coverred_pattern_3 = re.compile('^\s*(\d+)\s+input')
54    toggle_coverred_pattern_4 = re.compile('^\s*(\d+)\s+output')
55
56    not_toggle_coverred_pattern_1 = re.compile('^\s*(%0+)\s+reg')
57    not_toggle_coverred_pattern_2 = re.compile('^\s*(%0+)\s+wire')
58    not_toggle_coverred_pattern_3 = re.compile('^\s*(%0+)\s+input')
59    not_toggle_coverred_pattern_4 = re.compile('^\s*(%0+)\s+output')
60
61    line_cnt = 0
62
63    for line in lines:
64        line_coverred_match = line_coverred_pattern_1.search(line) or line_coverred_pattern_2.search(line)
65        not_line_coverred_match = not_line_coverred_pattern_1.search(line) or not_line_coverred_pattern_2.search(line)
66
67        assert not (line_coverred_match and not_line_coverred_match)
68
69        toggle_coverred_match = toggle_coverred_pattern_1.search(line) or toggle_coverred_pattern_2.search(line) or \
70                toggle_coverred_pattern_3.search(line) or toggle_coverred_pattern_4.search(line)
71        not_toggle_coverred_match = not_toggle_coverred_pattern_1.search(line) or not_toggle_coverred_pattern_2.search(line) or \
72                not_toggle_coverred_pattern_3.search(line) or not_toggle_coverred_pattern_4.search(line)
73
74        assert not (toggle_coverred_match and not_toggle_coverred_match)
75
76        all_match = (line_coverred_match, not_line_coverred_match,
77                toggle_coverred_match, not_toggle_coverred_match)
78        if not check_one_hot(all_match):
79            print("not_one_hot")
80            print(line_cnt)
81            print(all_match)
82            assert False, "This line matches multiple patterns"
83        if line_coverred_match:
84            line_annotations.append(LINE_COVERRED)
85        elif not_line_coverred_match:
86            line_annotations.append(NOT_LINE_COVERRED)
87        elif toggle_coverred_match:
88            line_annotations.append(TOGGLE_COVERRED)
89        elif not_toggle_coverred_match:
90            line_annotations.append(NOT_TOGGLE_COVERRED)
91        else:
92            line_annotations.append(DONTCARE)
93        line_cnt += 1
94    return line_annotations
95
96# get the line coverage statistics in line range [start, end)
97def get_coverage_statistics(line_annotations, start, end):
98    line_coverred = 0
99    not_line_coverred = 0
100    toggle_coverred = 0
101    not_toggle_coverred = 0
102    for i in range(start, end):
103        if line_annotations[i] == LINE_COVERRED:
104            line_coverred += 1
105
106        if line_annotations[i] == NOT_LINE_COVERRED:
107            not_line_coverred += 1
108
109        if line_annotations[i] == TOGGLE_COVERRED:
110            toggle_coverred += 1
111
112        if line_annotations[i] == NOT_TOGGLE_COVERRED:
113            not_toggle_coverred += 1
114
115    # deal with divide by zero
116    line_coverage = 1.0
117    if line_coverred + not_line_coverred != 0:
118        line_coverage = float(line_coverred) / (line_coverred + not_line_coverred)
119
120    toggle_coverage = 1.0
121    if toggle_coverred + not_toggle_coverred != 0:
122        toggle_coverage = float(toggle_coverred) / (toggle_coverred + not_toggle_coverred)
123    return ((line_coverred, not_line_coverred, line_coverage),
124            (toggle_coverred, not_toggle_coverred, toggle_coverage))
125
126# get modules and all it's submodules
127def get_modules(lines):
128    modules = {}
129
130    module_pattern = re.compile("module (\w+)\(")
131    endmodule_pattern = re.compile("endmodule")
132    submodule_pattern = re.compile("(\w+) (\w+) \( // @\[\w+.scala \d+:\d+\]")
133
134    line_count = 0
135
136    name = "ModuleName"
137
138    for line in lines:
139        module_match = module_pattern.search(line)
140        endmodule_match = endmodule_pattern.search(line)
141        submodule_match = submodule_pattern.search(line)
142
143        assert not (module_match and endmodule_match)
144
145        if module_match:
146            name = module_match.group(1)
147            # print("module_match: module: %s" % name)
148            assert name not in modules
149            # [begin
150            modules[name] = {}
151            modules[name][BEGIN] = line_count
152            # the first time we see a module, we treat as a root node
153            modules[name][TYPE] = ROOT
154
155        if endmodule_match:
156            # print("endmodule_match: module: %s" % name)
157            assert name in modules
158            assert END not in modules[name]
159            # end)
160            modules[name][END] = line_count + 1
161            # reset module name to invalid
162            name = "ModuleName"
163
164        if submodule_match:
165            # submodule must be inside hierarchy
166            assert name != "ModuleName"
167            submodule_type = submodule_match.group(1)
168            submodule_instance = submodule_match.group(2)
169            # print("submodule_match: type: %s instance: %s" % (submodule_type, submodule_instance))
170
171            # submodules should be defined first
172            # if we can not find it's definition
173            # we consider it a black block module
174            if submodule_type not in modules:
175                print("Module %s is a Blackbox" % submodule_type)
176            else:
177                # mark submodule as a tree node
178                # it's no longer root any more
179                modules[submodule_type][TYPE] = NODE
180
181                if CHILDREN not in modules[name]:
182                    modules[name][CHILDREN] = []
183                submodule = {MODULE: submodule_type, INSTANCE: submodule_instance}
184                modules[name][CHILDREN].append(submodule)
185
186        line_count += 1
187    return modules
188
189# we define two coverage metrics:
190# self coverage: coverage results of this module(excluding submodules)
191# tree coverage: coverage results of this module(including submodules)
192def get_tree_coverage(modules, coverage):
193    def dfs(module):
194        if TREECOVERAGE not in modules[module]:
195            self_coverage = modules[module][SELFCOVERAGE]
196            if CHILDREN not in modules[module]:
197                modules[module][TREECOVERAGE] = self_coverage
198            else:
199                line_coverred = self_coverage[LINECOVERAGE][0]
200                not_line_coverred = self_coverage[LINECOVERAGE][1]
201                toggle_coverred = self_coverage[TOGGLECOVERAGE][0]
202                not_toggle_coverred = self_coverage[TOGGLECOVERAGE][1]
203                # the dfs part
204                for child in modules[module][CHILDREN]:
205                    child_coverage = dfs(child[MODULE])
206                    line_coverred += child_coverage[LINECOVERAGE][0]
207                    not_line_coverred += child_coverage[LINECOVERAGE][1]
208                    toggle_coverred += child_coverage[TOGGLECOVERAGE][0]
209                    not_toggle_coverred += child_coverage[TOGGLECOVERAGE][1]
210                # deal with divide by zero
211                line_coverage = 1.0
212                if line_coverred + not_line_coverred != 0:
213                    line_coverage = float(line_coverred) / (line_coverred + not_line_coverred)
214                toggle_coverage = 1.0
215                if toggle_coverred + not_toggle_coverred != 0:
216                    toggle_coverage = float(toggle_coverred) / (toggle_coverred + not_toggle_coverred)
217                modules[module][TREECOVERAGE] = ((line_coverred, not_line_coverred, line_coverage),
218                        (toggle_coverred, not_toggle_coverred, toggle_coverage))
219        return modules[module][TREECOVERAGE]
220
221    for module in modules:
222        modules[module][SELFCOVERAGE] = coverage[module]
223
224    for module in modules:
225        modules[module][TREECOVERAGE] = dfs(module)
226    return modules
227
228# arg1: tree coverage results
229# arg2: coverage type
230def sort_coverage(coverage, self_or_tree, coverage_type):
231    l = [(module, coverage[module][self_or_tree][coverage_type])for module in coverage]
232    l.sort(key=lambda x:x[1][2])
233    return l
234
235def print_tree_coverage(tree_coverage):
236    def dfs(module, level):
237        # print current node
238        tree = tree_coverage[module][TREECOVERAGE]
239        self = tree_coverage[module][SELFCOVERAGE]
240        print("  " * level + "- " + module)
241        print("  " * level + "  tree_line", end="")
242        print("(%d, %d, %.2f)" % (tree[LINECOVERAGE][0], tree[LINECOVERAGE][1], tree[LINECOVERAGE][2] * 100.0))
243        print("  " * level + "  self_line", end="")
244        print("(%d, %d, %.2f)" % (self[LINECOVERAGE][0], self[LINECOVERAGE][1], self[LINECOVERAGE][2] * 100.0))
245
246        print("  " * level + "  tree_toggle", end="")
247        print("(%d, %d, %.2f)" % (tree[TOGGLECOVERAGE][0], tree[TOGGLECOVERAGE][1], tree[TOGGLECOVERAGE][2] * 100.0))
248        print("  " * level + "  self_toggle", end="")
249        print("(%d, %d, %.2f)" % (self[TOGGLECOVERAGE][0], self[TOGGLECOVERAGE][1], self[TOGGLECOVERAGE][2] * 100.0))
250
251        # print children nodes
252        if CHILDREN in modules[module]:
253                # the dfs part
254                for child in modules[module][CHILDREN]:
255                    dfs(child[MODULE], level + 1)
256
257    for module in tree_coverage:
258        if tree_coverage[module][TYPE] == ROOT:
259            dfs(module, 0)
260
261if __name__ == "__main__":
262    assert len(sys.argv) == 2, "Expect input_file"
263    input_file = sys.argv[1]
264    pp = pprint.PrettyPrinter(indent=4)
265
266    lines = get_lines(input_file)
267    # print("lines:")
268    # pp.pprint(lines)
269
270    annotations = get_line_annotation(lines)
271    # print("annotations:")
272    # pp.pprint(annotations)
273
274    modules = get_modules(lines)
275    # print("modules:")
276    # pp.pprint(modules)
277
278    self_coverage = {module: get_coverage_statistics(annotations, modules[module][BEGIN], modules[module][END])
279            for module in modules}
280    # print("self_coverage:")
281    # pp.pprint(self_coverage)
282
283    tree_coverage = get_tree_coverage(modules, self_coverage)
284    # print("tree_coverage:")
285    # pp.pprint(tree_coverage)
286
287    print("LineSelfCoverage:")
288    pp.pprint(sort_coverage(tree_coverage, SELFCOVERAGE, LINECOVERAGE))
289    print("LineTreeCoverage:")
290    pp.pprint(sort_coverage(tree_coverage, TREECOVERAGE, LINECOVERAGE))
291
292    print("ToggleSelfCoverage:")
293    pp.pprint(sort_coverage(tree_coverage, SELFCOVERAGE, TOGGLECOVERAGE))
294    print("ToggleTreeCoverage:")
295    pp.pprint(sort_coverage(tree_coverage, TREECOVERAGE, TOGGLECOVERAGE))
296
297    print("AllCoverage:")
298    print_tree_coverage(tree_coverage)
299