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