xref: /XiangShan/scripts/coverage/statistics.py (revision c6d439803a044ea209139672b25e35fe8d7f4aa0)
1d70a2272SAllen#/usr/bin/python3
2d70a2272SAllen# -*- coding: UTF-8 -*-
3*c6d43980SLemover
4*c6d43980SLemover#***************************************************************************************
5*c6d43980SLemover# Copyright (c) 2020-2021 Institute of Computing Technology, Chinese Academy of Sciences
6*c6d43980SLemover#
7*c6d43980SLemover# XiangShan is licensed under Mulan PSL v2.
8*c6d43980SLemover# You can use this software according to the terms and conditions of the Mulan PSL v2.
9*c6d43980SLemover# You may obtain a copy of Mulan PSL v2 at:
10*c6d43980SLemover#          http://license.coscl.org.cn/MulanPSL2
11*c6d43980SLemover#
12*c6d43980SLemover# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
13*c6d43980SLemover# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
14*c6d43980SLemover# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
15*c6d43980SLemover#
16*c6d43980SLemover# See the Mulan PSL v2 for more details.
17*c6d43980SLemover#***************************************************************************************
18*c6d43980SLemover
19*c6d43980SLemover
20d70a2272SAllenimport sys
21d70a2272SAllenimport re
22d70a2272SAllenimport copy
2343676441SAllenimport pprint
24d70a2272SAllen
25c8b1e4dbSAllenLINE_COVERRED = "LINE_COVERRED"
26c8b1e4dbSAllenNOT_LINE_COVERRED = "NOT_LINE_COVERRED"
27c8b1e4dbSAllenTOGGLE_COVERRED = "TOGGLE_COVERRED"
28c8b1e4dbSAllenNOT_TOGGLE_COVERRED = "NOT_TOGGLE_COVERRED"
2943676441SAllenDONTCARE = "DONTCARE"
30c8b1e4dbSAllen
3143676441SAllenBEGIN = "BEGIN"
3243676441SAllenEND = "END"
3343676441SAllenCHILDREN = "CHILDREN"
3443676441SAllenMODULE = "MODULE"
3543676441SAllenINSTANCE = "INSTANCE"
3643676441SAllenTYPE = "TYPE"
3743676441SAllenROOT = "ROOT"
3843676441SAllenNODE = "NODE"
3943676441SAllenSELFCOVERAGE = "SELFCOVERAGE"
4043676441SAllenTREECOVERAGE = "TREECOVERAGE"
41c8b1e4dbSAllenLINECOVERAGE = 0
42c8b1e4dbSAllenTOGGLECOVERAGE = 1
43c8b1e4dbSAllen
44c8b1e4dbSAllendef check_one_hot(l):
45c8b1e4dbSAllen    cnt = 0
46c8b1e4dbSAllen    for e in l:
47c8b1e4dbSAllen        if e:
48c8b1e4dbSAllen            cnt += 1
49c8b1e4dbSAllen    return cnt <= 1
5043676441SAllen
5143676441SAllendef get_lines(input_file):
5243676441SAllen    lines = []
53d70a2272SAllen    with open(input_file) as f:
54d70a2272SAllen        for line in f:
5543676441SAllen            lines.append(line)
5643676441SAllen    return lines
57d70a2272SAllen
5843676441SAllendef get_line_annotation(lines):
5943676441SAllen    line_annotations = []
6043676441SAllen    # pattern_1: 040192     if(array_0_MPORT_en & array_0_MPORT_mask) begin
6143676441SAllen    # pattern_2: 2218110        end else if (_T_30) begin // @[Conditional.scala 40:58]
6243676441SAllen    # pattern_2: 000417     end else begin
63c8b1e4dbSAllen    line_coverred_pattern_1 = re.compile('^\s*(\d+)\s+if')
64c8b1e4dbSAllen    line_coverred_pattern_2 = re.compile('^\s*(\d+)\s+end else')
65c8b1e4dbSAllen    not_line_coverred_pattern_1 = re.compile('^\s*(%0+)\s+if')
66c8b1e4dbSAllen    not_line_coverred_pattern_2 = re.compile('^\s*(%0+)\s+end else')
67c8b1e4dbSAllen
68c8b1e4dbSAllen    toggle_coverred_pattern_1 = re.compile('^\s*(\d+)\s+reg')
69c8b1e4dbSAllen    toggle_coverred_pattern_2 = re.compile('^\s*(\d+)\s+wire')
70c8b1e4dbSAllen    toggle_coverred_pattern_3 = re.compile('^\s*(\d+)\s+input')
71c8b1e4dbSAllen    toggle_coverred_pattern_4 = re.compile('^\s*(\d+)\s+output')
72c8b1e4dbSAllen
73c8b1e4dbSAllen    not_toggle_coverred_pattern_1 = re.compile('^\s*(%0+)\s+reg')
74c8b1e4dbSAllen    not_toggle_coverred_pattern_2 = re.compile('^\s*(%0+)\s+wire')
75c8b1e4dbSAllen    not_toggle_coverred_pattern_3 = re.compile('^\s*(%0+)\s+input')
76c8b1e4dbSAllen    not_toggle_coverred_pattern_4 = re.compile('^\s*(%0+)\s+output')
77c8b1e4dbSAllen
78c8b1e4dbSAllen    line_cnt = 0
7943676441SAllen
8043676441SAllen    for line in lines:
81c8b1e4dbSAllen        line_coverred_match = line_coverred_pattern_1.search(line) or line_coverred_pattern_2.search(line)
82c8b1e4dbSAllen        not_line_coverred_match = not_line_coverred_pattern_1.search(line) or not_line_coverred_pattern_2.search(line)
83d70a2272SAllen
84c8b1e4dbSAllen        assert not (line_coverred_match and not_line_coverred_match)
85d70a2272SAllen
86c8b1e4dbSAllen        toggle_coverred_match = toggle_coverred_pattern_1.search(line) or toggle_coverred_pattern_2.search(line) or \
87c8b1e4dbSAllen                toggle_coverred_pattern_3.search(line) or toggle_coverred_pattern_4.search(line)
88c8b1e4dbSAllen        not_toggle_coverred_match = not_toggle_coverred_pattern_1.search(line) or not_toggle_coverred_pattern_2.search(line) or \
89c8b1e4dbSAllen                not_toggle_coverred_pattern_3.search(line) or not_toggle_coverred_pattern_4.search(line)
90c8b1e4dbSAllen
91c8b1e4dbSAllen        assert not (toggle_coverred_match and not_toggle_coverred_match)
92c8b1e4dbSAllen
93c8b1e4dbSAllen        all_match = (line_coverred_match, not_line_coverred_match,
94c8b1e4dbSAllen                toggle_coverred_match, not_toggle_coverred_match)
95c8b1e4dbSAllen        if not check_one_hot(all_match):
96c8b1e4dbSAllen            print("not_one_hot")
97c8b1e4dbSAllen            print(line_cnt)
98c8b1e4dbSAllen            print(all_match)
99c8b1e4dbSAllen            assert False, "This line matches multiple patterns"
100c8b1e4dbSAllen        if line_coverred_match:
101c8b1e4dbSAllen            line_annotations.append(LINE_COVERRED)
102c8b1e4dbSAllen        elif not_line_coverred_match:
103c8b1e4dbSAllen            line_annotations.append(NOT_LINE_COVERRED)
104c8b1e4dbSAllen        elif toggle_coverred_match:
105c8b1e4dbSAllen            line_annotations.append(TOGGLE_COVERRED)
106c8b1e4dbSAllen        elif not_toggle_coverred_match:
107c8b1e4dbSAllen            line_annotations.append(NOT_TOGGLE_COVERRED)
10843676441SAllen        else:
10943676441SAllen            line_annotations.append(DONTCARE)
110c8b1e4dbSAllen        line_cnt += 1
11143676441SAllen    return line_annotations
11243676441SAllen
11343676441SAllen# get the line coverage statistics in line range [start, end)
11443676441SAllendef get_coverage_statistics(line_annotations, start, end):
115c8b1e4dbSAllen    line_coverred = 0
116c8b1e4dbSAllen    not_line_coverred = 0
117c8b1e4dbSAllen    toggle_coverred = 0
118c8b1e4dbSAllen    not_toggle_coverred = 0
11943676441SAllen    for i in range(start, end):
120c8b1e4dbSAllen        if line_annotations[i] == LINE_COVERRED:
121c8b1e4dbSAllen            line_coverred += 1
122d70a2272SAllen
123c8b1e4dbSAllen        if line_annotations[i] == NOT_LINE_COVERRED:
124c8b1e4dbSAllen            not_line_coverred += 1
125c8b1e4dbSAllen
126c8b1e4dbSAllen        if line_annotations[i] == TOGGLE_COVERRED:
127c8b1e4dbSAllen            toggle_coverred += 1
128c8b1e4dbSAllen
129c8b1e4dbSAllen        if line_annotations[i] == NOT_TOGGLE_COVERRED:
130c8b1e4dbSAllen            not_toggle_coverred += 1
13143676441SAllen
13243676441SAllen    # deal with divide by zero
133c8b1e4dbSAllen    line_coverage = 1.0
134c8b1e4dbSAllen    if line_coverred + not_line_coverred != 0:
135c8b1e4dbSAllen        line_coverage = float(line_coverred) / (line_coverred + not_line_coverred)
136c8b1e4dbSAllen
137c8b1e4dbSAllen    toggle_coverage = 1.0
138c8b1e4dbSAllen    if toggle_coverred + not_toggle_coverred != 0:
139c8b1e4dbSAllen        toggle_coverage = float(toggle_coverred) / (toggle_coverred + not_toggle_coverred)
140c8b1e4dbSAllen    return ((line_coverred, not_line_coverred, line_coverage),
141c8b1e4dbSAllen            (toggle_coverred, not_toggle_coverred, toggle_coverage))
14243676441SAllen
14343676441SAllen# get modules and all it's submodules
14443676441SAllendef get_modules(lines):
14543676441SAllen    modules = {}
14643676441SAllen
14743676441SAllen    module_pattern = re.compile("module (\w+)\(")
14843676441SAllen    endmodule_pattern = re.compile("endmodule")
14943676441SAllen    submodule_pattern = re.compile("(\w+) (\w+) \( // @\[\w+.scala \d+:\d+\]")
15043676441SAllen
15143676441SAllen    line_count = 0
15243676441SAllen
15343676441SAllen    name = "ModuleName"
15443676441SAllen
15543676441SAllen    for line in lines:
15643676441SAllen        module_match = module_pattern.search(line)
15743676441SAllen        endmodule_match = endmodule_pattern.search(line)
15843676441SAllen        submodule_match = submodule_pattern.search(line)
15943676441SAllen
16043676441SAllen        assert not (module_match and endmodule_match)
16143676441SAllen
16243676441SAllen        if module_match:
16343676441SAllen            name = module_match.group(1)
16443676441SAllen            # print("module_match: module: %s" % name)
16543676441SAllen            assert name not in modules
16643676441SAllen            # [begin
16743676441SAllen            modules[name] = {}
16843676441SAllen            modules[name][BEGIN] = line_count
16943676441SAllen            # the first time we see a module, we treat as a root node
17043676441SAllen            modules[name][TYPE] = ROOT
17143676441SAllen
17243676441SAllen        if endmodule_match:
17343676441SAllen            # print("endmodule_match: module: %s" % name)
17443676441SAllen            assert name in modules
17543676441SAllen            assert END not in modules[name]
17643676441SAllen            # end)
17743676441SAllen            modules[name][END] = line_count + 1
17843676441SAllen            # reset module name to invalid
17943676441SAllen            name = "ModuleName"
18043676441SAllen
18143676441SAllen        if submodule_match:
18243676441SAllen            # submodule must be inside hierarchy
18343676441SAllen            assert name != "ModuleName"
18443676441SAllen            submodule_type = submodule_match.group(1)
18543676441SAllen            submodule_instance = submodule_match.group(2)
18643676441SAllen            # print("submodule_match: type: %s instance: %s" % (submodule_type, submodule_instance))
18743676441SAllen
18843676441SAllen            # submodules should be defined first
18943676441SAllen            # if we can not find it's definition
19043676441SAllen            # we consider it a black block module
19143676441SAllen            if submodule_type not in modules:
19243676441SAllen                print("Module %s is a Blackbox" % submodule_type)
19343676441SAllen            else:
19443676441SAllen                # mark submodule as a tree node
19543676441SAllen                # it's no longer root any more
19643676441SAllen                modules[submodule_type][TYPE] = NODE
19743676441SAllen
19843676441SAllen                if CHILDREN not in modules[name]:
19943676441SAllen                    modules[name][CHILDREN] = []
20043676441SAllen                submodule = {MODULE: submodule_type, INSTANCE: submodule_instance}
20143676441SAllen                modules[name][CHILDREN].append(submodule)
20243676441SAllen
20343676441SAllen        line_count += 1
20443676441SAllen    return modules
20543676441SAllen
20643676441SAllen# we define two coverage metrics:
20743676441SAllen# self coverage: coverage results of this module(excluding submodules)
20843676441SAllen# tree coverage: coverage results of this module(including submodules)
20943676441SAllendef get_tree_coverage(modules, coverage):
21043676441SAllen    def dfs(module):
21143676441SAllen        if TREECOVERAGE not in modules[module]:
21243676441SAllen            self_coverage = modules[module][SELFCOVERAGE]
21343676441SAllen            if CHILDREN not in modules[module]:
21443676441SAllen                modules[module][TREECOVERAGE] = self_coverage
21543676441SAllen            else:
216c8b1e4dbSAllen                line_coverred = self_coverage[LINECOVERAGE][0]
217c8b1e4dbSAllen                not_line_coverred = self_coverage[LINECOVERAGE][1]
218c8b1e4dbSAllen                toggle_coverred = self_coverage[TOGGLECOVERAGE][0]
219c8b1e4dbSAllen                not_toggle_coverred = self_coverage[TOGGLECOVERAGE][1]
22043676441SAllen                # the dfs part
22143676441SAllen                for child in modules[module][CHILDREN]:
22243676441SAllen                    child_coverage = dfs(child[MODULE])
223c8b1e4dbSAllen                    line_coverred += child_coverage[LINECOVERAGE][0]
224c8b1e4dbSAllen                    not_line_coverred += child_coverage[LINECOVERAGE][1]
225c8b1e4dbSAllen                    toggle_coverred += child_coverage[TOGGLECOVERAGE][0]
226c8b1e4dbSAllen                    not_toggle_coverred += child_coverage[TOGGLECOVERAGE][1]
22743676441SAllen                # deal with divide by zero
228c8b1e4dbSAllen                line_coverage = 1.0
229c8b1e4dbSAllen                if line_coverred + not_line_coverred != 0:
230c8b1e4dbSAllen                    line_coverage = float(line_coverred) / (line_coverred + not_line_coverred)
231c8b1e4dbSAllen                toggle_coverage = 1.0
232c8b1e4dbSAllen                if toggle_coverred + not_toggle_coverred != 0:
233c8b1e4dbSAllen                    toggle_coverage = float(toggle_coverred) / (toggle_coverred + not_toggle_coverred)
234c8b1e4dbSAllen                modules[module][TREECOVERAGE] = ((line_coverred, not_line_coverred, line_coverage),
235c8b1e4dbSAllen                        (toggle_coverred, not_toggle_coverred, toggle_coverage))
23643676441SAllen        return modules[module][TREECOVERAGE]
23743676441SAllen
23843676441SAllen    for module in modules:
23943676441SAllen        modules[module][SELFCOVERAGE] = coverage[module]
24043676441SAllen
24143676441SAllen    for module in modules:
24243676441SAllen        modules[module][TREECOVERAGE] = dfs(module)
24343676441SAllen    return modules
24443676441SAllen
24543676441SAllen# arg1: tree coverage results
24643676441SAllen# arg2: coverage type
247c8b1e4dbSAllendef sort_coverage(coverage, self_or_tree, coverage_type):
248c8b1e4dbSAllen    l = [(module, coverage[module][self_or_tree][coverage_type])for module in coverage]
24943676441SAllen    l.sort(key=lambda x:x[1][2])
25043676441SAllen    return l
25143676441SAllen
25243676441SAllendef print_tree_coverage(tree_coverage):
25343676441SAllen    def dfs(module, level):
25443676441SAllen        # print current node
25543676441SAllen        tree = tree_coverage[module][TREECOVERAGE]
25643676441SAllen        self = tree_coverage[module][SELFCOVERAGE]
25743676441SAllen        print("  " * level + "- " + module)
258c8b1e4dbSAllen        print("  " * level + "  tree_line", end="")
259c8b1e4dbSAllen        print("(%d, %d, %.2f)" % (tree[LINECOVERAGE][0], tree[LINECOVERAGE][1], tree[LINECOVERAGE][2] * 100.0))
260c8b1e4dbSAllen        print("  " * level + "  self_line", end="")
261c8b1e4dbSAllen        print("(%d, %d, %.2f)" % (self[LINECOVERAGE][0], self[LINECOVERAGE][1], self[LINECOVERAGE][2] * 100.0))
262c8b1e4dbSAllen
263c8b1e4dbSAllen        print("  " * level + "  tree_toggle", end="")
264c8b1e4dbSAllen        print("(%d, %d, %.2f)" % (tree[TOGGLECOVERAGE][0], tree[TOGGLECOVERAGE][1], tree[TOGGLECOVERAGE][2] * 100.0))
265c8b1e4dbSAllen        print("  " * level + "  self_toggle", end="")
266c8b1e4dbSAllen        print("(%d, %d, %.2f)" % (self[TOGGLECOVERAGE][0], self[TOGGLECOVERAGE][1], self[TOGGLECOVERAGE][2] * 100.0))
26743676441SAllen
26843676441SAllen        # print children nodes
26943676441SAllen        if CHILDREN in modules[module]:
27043676441SAllen                # the dfs part
27143676441SAllen                for child in modules[module][CHILDREN]:
27243676441SAllen                    dfs(child[MODULE], level + 1)
27343676441SAllen
27443676441SAllen    for module in tree_coverage:
27543676441SAllen        if tree_coverage[module][TYPE] == ROOT:
27643676441SAllen            dfs(module, 0)
27743676441SAllen
27843676441SAllenif __name__ == "__main__":
27943676441SAllen    assert len(sys.argv) == 2, "Expect input_file"
28043676441SAllen    input_file = sys.argv[1]
28143676441SAllen    pp = pprint.PrettyPrinter(indent=4)
28243676441SAllen
28343676441SAllen    lines = get_lines(input_file)
28443676441SAllen    # print("lines:")
28543676441SAllen    # pp.pprint(lines)
28643676441SAllen
28743676441SAllen    annotations = get_line_annotation(lines)
28843676441SAllen    # print("annotations:")
28943676441SAllen    # pp.pprint(annotations)
29043676441SAllen
29143676441SAllen    modules = get_modules(lines)
29243676441SAllen    # print("modules:")
29343676441SAllen    # pp.pprint(modules)
29443676441SAllen
29543676441SAllen    self_coverage = {module: get_coverage_statistics(annotations, modules[module][BEGIN], modules[module][END])
29643676441SAllen            for module in modules}
29743676441SAllen    # print("self_coverage:")
29843676441SAllen    # pp.pprint(self_coverage)
29943676441SAllen
30043676441SAllen    tree_coverage = get_tree_coverage(modules, self_coverage)
30143676441SAllen    # print("tree_coverage:")
30243676441SAllen    # pp.pprint(tree_coverage)
30343676441SAllen
304c8b1e4dbSAllen    print("LineSelfCoverage:")
305c8b1e4dbSAllen    pp.pprint(sort_coverage(tree_coverage, SELFCOVERAGE, LINECOVERAGE))
306c8b1e4dbSAllen    print("LineTreeCoverage:")
307c8b1e4dbSAllen    pp.pprint(sort_coverage(tree_coverage, TREECOVERAGE, LINECOVERAGE))
30843676441SAllen
309c8b1e4dbSAllen    print("ToggleSelfCoverage:")
310c8b1e4dbSAllen    pp.pprint(sort_coverage(tree_coverage, SELFCOVERAGE, TOGGLECOVERAGE))
311c8b1e4dbSAllen    print("ToggleTreeCoverage:")
312c8b1e4dbSAllen    pp.pprint(sort_coverage(tree_coverage, TREECOVERAGE, TOGGLECOVERAGE))
31343676441SAllen
31443676441SAllen    print("AllCoverage:")
31543676441SAllen    print_tree_coverage(tree_coverage)
316