xref: /XiangShan/scripts/coverage/statistics.py (revision 49272fa467f97c3293eb9ed685e99ecf79691182)
1#/usr/bin/python3
2# -*- coding: UTF-8 -*-
3import sys
4import re
5import copy
6import pprint
7
8COVERRED = "COVERRED"
9NOT_COVERRED = "NOT_COVERRED"
10DONTCARE = "DONTCARE"
11BEGIN = "BEGIN"
12END = "END"
13CHILDREN = "CHILDREN"
14MODULE = "MODULE"
15INSTANCE = "INSTANCE"
16TYPE="TYPE"
17ROOT="ROOT"
18NODE="NODE"
19SELFCOVERAGE="SELFCOVERAGE"
20TREECOVERAGE="TREECOVERAGE"
21
22def get_lines(input_file):
23    lines = []
24    with open(input_file) as f:
25        for line in f:
26            lines.append(line)
27    return lines
28
29def get_line_annotation(lines):
30    line_annotations = []
31    # pattern_1: 040192     if(array_0_MPORT_en & array_0_MPORT_mask) begin
32    # pattern_2: 2218110        end else if (_T_30) begin // @[Conditional.scala 40:58]
33    # pattern_2: 000417     end else begin
34    coverred_pattern_1 = re.compile('^\s*(\d+)\s+if')
35    coverred_pattern_2 = re.compile('^\s*(\d+)\s+end else')
36    not_coverred_pattern_1 = re.compile('^\s*(%0+)\s+if')
37    not_coverred_pattern_2 = re.compile('^\s*(%0+)\s+end else')
38
39    for line in lines:
40        coverred_match = coverred_pattern_1.search(line) or coverred_pattern_2.search(line)
41        not_coverred_match = not_coverred_pattern_1.search(line) or not_coverred_pattern_2.search(line)
42
43        assert not (coverred_match and not_coverred_match)
44
45        if coverred_match:
46            line_annotations.append(COVERRED)
47        elif not_coverred_match:
48            line_annotations.append(NOT_COVERRED)
49        else:
50            line_annotations.append(DONTCARE)
51    return line_annotations
52
53# get the line coverage statistics in line range [start, end)
54def get_coverage_statistics(line_annotations, start, end):
55    coverred = 0
56    not_coverred = 0
57    for i in range(start, end):
58        if line_annotations[i] == COVERRED:
59            coverred += 1
60
61        if line_annotations[i] == NOT_COVERRED:
62            not_coverred += 1
63
64    # deal with divide by zero
65    coverage = 1.0
66    if coverred + not_coverred != 0:
67        coverage = float(coverred) / (coverred + not_coverred)
68    return (coverred, not_coverred, coverage)
69
70# get modules and all it's submodules
71def get_modules(lines):
72    modules = {}
73
74    module_pattern = re.compile("module (\w+)\(")
75    endmodule_pattern = re.compile("endmodule")
76    submodule_pattern = re.compile("(\w+) (\w+) \( // @\[\w+.scala \d+:\d+\]")
77
78    line_count = 0
79
80    name = "ModuleName"
81
82    for line in lines:
83        module_match = module_pattern.search(line)
84        endmodule_match = endmodule_pattern.search(line)
85        submodule_match = submodule_pattern.search(line)
86
87        assert not (module_match and endmodule_match)
88
89        if module_match:
90            name = module_match.group(1)
91            # print("module_match: module: %s" % name)
92            assert name not in modules
93            # [begin
94            modules[name] = {}
95            modules[name][BEGIN] = line_count
96            # the first time we see a module, we treat as a root node
97            modules[name][TYPE] = ROOT
98
99        if endmodule_match:
100            # print("endmodule_match: module: %s" % name)
101            assert name in modules
102            assert END not in modules[name]
103            # end)
104            modules[name][END] = line_count + 1
105            # reset module name to invalid
106            name = "ModuleName"
107
108        if submodule_match:
109            # submodule must be inside hierarchy
110            assert name != "ModuleName"
111            submodule_type = submodule_match.group(1)
112            submodule_instance = submodule_match.group(2)
113            # print("submodule_match: type: %s instance: %s" % (submodule_type, submodule_instance))
114
115            # submodules should be defined first
116            # if we can not find it's definition
117            # we consider it a black block module
118            if submodule_type not in modules:
119                print("Module %s is a Blackbox" % submodule_type)
120            else:
121                # mark submodule as a tree node
122                # it's no longer root any more
123                modules[submodule_type][TYPE] = NODE
124
125                if CHILDREN not in modules[name]:
126                    modules[name][CHILDREN] = []
127                submodule = {MODULE: submodule_type, INSTANCE: submodule_instance}
128                modules[name][CHILDREN].append(submodule)
129
130        line_count += 1
131    return modules
132
133# we define two coverage metrics:
134# self coverage: coverage results of this module(excluding submodules)
135# tree coverage: coverage results of this module(including submodules)
136def get_tree_coverage(modules, coverage):
137    def dfs(module):
138        if TREECOVERAGE not in modules[module]:
139            self_coverage = modules[module][SELFCOVERAGE]
140            if CHILDREN not in modules[module]:
141                modules[module][TREECOVERAGE] = self_coverage
142            else:
143                coverred = self_coverage[0]
144                not_coverred = self_coverage[1]
145                # the dfs part
146                for child in modules[module][CHILDREN]:
147                    child_coverage = dfs(child[MODULE])
148                    coverred += child_coverage[0]
149                    not_coverred += child_coverage[1]
150                # deal with divide by zero
151                coverage = 1.0
152                if coverred + not_coverred != 0:
153                    coverage = float(coverred) / (coverred + not_coverred)
154                modules[module][TREECOVERAGE] = (coverred, not_coverred, coverage)
155        return modules[module][TREECOVERAGE]
156
157    for module in modules:
158        modules[module][SELFCOVERAGE] = coverage[module]
159
160    for module in modules:
161        modules[module][TREECOVERAGE] = dfs(module)
162    return modules
163
164# arg1: tree coverage results
165# arg2: coverage type
166def sort_coverage(coverage, coverage_type):
167    l = [(module, coverage[module][coverage_type])for module in coverage]
168    l.sort(key=lambda x:x[1][2])
169    return l
170
171def print_tree_coverage(tree_coverage):
172    def dfs(module, level):
173        # print current node
174        tree = tree_coverage[module][TREECOVERAGE]
175        self = tree_coverage[module][SELFCOVERAGE]
176        print("  " * level + "- " + module)
177        print("  " * level + "  tree", end="")
178        print("(%d, %d, %.2f)" % (tree[0], tree[1], tree[2] * 100.0))
179        print("  " * level + "  self", end="")
180        print("(%d, %d, %.2f)" % (self[0], self[1], self[2] * 100.0))
181
182        # print children nodes
183        if CHILDREN in modules[module]:
184                # the dfs part
185                for child in modules[module][CHILDREN]:
186                    dfs(child[MODULE], level + 1)
187
188    for module in tree_coverage:
189        if tree_coverage[module][TYPE] == ROOT:
190            dfs(module, 0)
191
192if __name__ == "__main__":
193    assert len(sys.argv) == 2, "Expect input_file"
194    input_file = sys.argv[1]
195    pp = pprint.PrettyPrinter(indent=4)
196
197    lines = get_lines(input_file)
198    # print("lines:")
199    # pp.pprint(lines)
200
201    annotations = get_line_annotation(lines)
202    # print("annotations:")
203    # pp.pprint(annotations)
204
205    modules = get_modules(lines)
206    # print("modules:")
207    # pp.pprint(modules)
208
209    self_coverage = {module: get_coverage_statistics(annotations, modules[module][BEGIN], modules[module][END])
210            for module in modules}
211    # print("self_coverage:")
212    # pp.pprint(self_coverage)
213
214    tree_coverage = get_tree_coverage(modules, self_coverage)
215    # print("tree_coverage:")
216    # pp.pprint(tree_coverage)
217
218    print("SelfCoverage:")
219    pp.pprint(sort_coverage(tree_coverage, SELFCOVERAGE))
220
221    print("TreeCoverage:")
222    pp.pprint(sort_coverage(tree_coverage, TREECOVERAGE))
223
224    print("AllCoverage:")
225    print_tree_coverage(tree_coverage)
226