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