xref: /XiangShan/scripts/xiangshan.py (revision 708ceed4afe43fb0ea3a52407e46b2794c573634)
1#***************************************************************************************
2# Copyright (c) 2020-2021 Institute of Computing Technology, Chinese Academy of Sciences
3# Copyright (c) 2020-2021 Peng Cheng Laboratory
4#
5# XiangShan is licensed under Mulan PSL v2.
6# You can use this software according to the terms and conditions of the Mulan PSL v2.
7# You may obtain a copy of Mulan PSL v2 at:
8#          http://license.coscl.org.cn/MulanPSL2
9#
10# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
11# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
12# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
13#
14# See the Mulan PSL v2 for more details.
15#***************************************************************************************
16
17# Simple version of xiangshan python wrapper
18
19import argparse
20import os
21import random
22import subprocess
23import sys
24
25
26class XSArgs(object):
27    script_path = os.path.realpath(__file__)
28    # default path to the repositories
29    noop_home = os.path.join(os.path.dirname(script_path), "..")
30    nemu_home = os.path.join(noop_home, "../NEMU")
31    am_home = os.path.join(noop_home, "../nexus-am")
32    dramsim3_home = os.path.join(noop_home, "../DRAMsim3")
33    rvtest_home = os.path.join(noop_home, "../riscv-tests")
34
35    def __init__(self, args):
36        # all path environment variables that should be set
37        all_path = [
38            # (python argument, environment variable, default, target function)
39            (None, "NOOP_HOME", self.noop_home, self.set_noop_home),
40            (args.nemu, "NEMU_HOME", self.nemu_home, self.set_nemu_home),
41            (args.am, "AM_HOME", self.am_home, self.set_am_home),
42            (args.dramsim3, "DRAMSIM3_HOME", self.dramsim3_home, self.set_dramsim3_home),
43            (args.rvtest, "RVTEST_HOME", self.rvtest_home, self.set_rvtest_home),
44        ]
45        for (arg_in, env, default, set_func) in all_path:
46            set_func(self.__extract_path(arg_in, env, default))
47        # Chisel arguments
48        self.disable_log = args.disable_log
49        self.dual_core = args.dual_core
50        # Makefile arguments
51        self.threads = args.threads
52        self.with_dramsim3 = 1 if args.with_dramsim3 else None
53        self.trace = 1 if args.trace else None
54        self.config = args.config
55        # emu arguments
56        self.max_instr = args.max_instr
57        self.seed = random.randint(0, 9999)
58        self.numa = args.numa
59
60    def get_env_variables(self):
61        all_env = {
62            "NOOP_HOME"    : self.noop_home,
63            "NEMU_HOME"    : self.nemu_home,
64            "AM_HOME"      : self.am_home,
65            "DRAMSIM3_HOME": self.dramsim3_home
66        }
67        return all_env
68
69    def get_chisel_args(self, prefix=None):
70        chisel_args = [
71            (self.disable_log, "disable-log"),
72            (self.dual_core,   "dual-core")
73        ]
74        args = map(lambda x: x[1], filter(lambda arg: arg[0], chisel_args))
75        if prefix is not None:
76            args = map(lambda x: prefix + x, args)
77        return args
78
79    def get_makefile_args(self):
80        makefile_args = [
81
82
83            (self.threads, "EMU_THREADS"),
84            (self.with_dramsim3, "WITH_DRAMSIM3"),
85            (self.trace, "EMU_TRACE"),
86            (self.config, "CONFIG")
87        ]
88        args = filter(lambda arg: arg[0] is not None, makefile_args)
89        return args
90
91    def get_emu_args(self):
92        emu_args = [
93            (self.max_instr, "max-instr"),
94            (self.seed,      "seed")
95        ]
96        args = filter(lambda arg: arg[0] is not None, emu_args)
97        return args
98
99    def show(self):
100        print("Extra environment variables:")
101        env = self.get_env_variables()
102        for env_name in env:
103            print(f"{env_name}: {env[env_name]}")
104        print()
105        print("Chisel arguments:")
106        print(" ".join(self.get_chisel_args()))
107        print()
108        print("Makefile arguments:")
109        for val, name in self.get_makefile_args():
110            print(f"{name}={val}")
111        print()
112        print("emu arguments:")
113        for val, name in self.get_emu_args():
114            print(f"--{name} {val}")
115        print()
116
117    def __extract_path(self, path, env=None, default=None):
118        if path is None and env is not None:
119            path = os.getenv(env)
120        if path is None and default is not None:
121            path = default
122        path = os.path.realpath(path)
123        return path
124
125    def set_noop_home(self, path):
126        self.noop_home = path
127
128    def set_nemu_home(self, path):
129        self.nemu_home = path
130
131    def set_am_home(self, path):
132        self.am_home = path
133
134    def set_dramsim3_home(self, path):
135        self.dramsim3_home = path
136
137    def set_rvtest_home(self, path):
138        self.rvtest_home = path
139
140# XiangShan environment
141class XiangShan(object):
142    def __init__(self, args):
143        self.args = XSArgs(args)
144
145    def show(self):
146        self.args.show()
147
148    def generate_verilog(self):
149        print("Generating XiangShan verilog with the following configurations:")
150        self.show()
151        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
152        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
153        return_code = self.__exec_cmd(f'make -C $NOOP_HOME verilog SIM_ARGS="{sim_args}" {make_args}')
154        return return_code
155
156    def build_emu(self):
157        print("Building XiangShan emu with the following configurations:")
158        self.show()
159        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
160        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
161        return_code = self.__exec_cmd(f'make -C $NOOP_HOME emu -j200 SIM_ARGS="{sim_args}" {make_args}')
162        return return_code
163
164    def run_emu(self, workload):
165        print("Running XiangShan emu with the following configurations:")
166        self.show()
167        emu_args = " ".join(map(lambda arg: f"--{arg[1]} {arg[0]}", self.args.get_emu_args()))
168        print("workload:", workload)
169        numa_args = f"numactl -m 1 -C 64-{64+self.args.threads-1}" if self.args.numa else ""
170        return_code = self.__exec_cmd(f'{numa_args} $NOOP_HOME/build/emu -i {workload} {emu_args}')
171        return return_code
172
173    def run(self, args):
174        if args.ci is not None:
175            return self.run_ci(args.ci)
176        actions = [
177            (args.generate, lambda _ : self.generate_verilog()),
178            (args.build, lambda _ : self.build_emu()),
179            (args.workload, lambda args: self.run_emu(args.workload))
180        ]
181        valid_actions = map(lambda act: act[1], filter(lambda act: act[0], actions))
182        for i, action in enumerate(valid_actions):
183            print(f"Action {i}:")
184            ret = action(args)
185            if ret:
186                return ret
187        return 0
188
189    def __exec_cmd(self, cmd):
190        env = dict(os.environ)
191        env.update(self.args.get_env_variables())
192        print("subprocess call cmd:", cmd)
193        return_code = subprocess.call(cmd, shell=True, env=env)
194        return return_code
195
196    def __get_ci_cputest(self, name=None):
197        base_dir = os.path.join(self.args.am_home, "tests/cputest/build")
198        cputest = os.listdir(base_dir)
199        cputest = filter(lambda x: x.endswith(".bin"), cputest)
200        cputest = map(lambda x: os.path.join(base_dir, x), cputest)
201        return cputest
202
203    def __get_ci_rvtest(self, name=None):
204        base_dir = os.path.join(self.args.rvtest_home, "isa/build")
205        riscv_tests = os.listdir(base_dir)
206        riscv_tests = filter(lambda x: x.endswith(".bin"), riscv_tests)
207        all_rv_tests = ["rv64ui", "rv64um", "rv64ua", "rv64uf", "rv64ud"]
208        riscv_tests = filter(lambda x: x[:6] in all_rv_tests, riscv_tests)
209        riscv_tests = map(lambda x: os.path.join(base_dir, x), riscv_tests)
210        return riscv_tests
211
212    def __get_ci_misc(self, name=None):
213        base_dir = "/home/ci-runner/xsenv/workloads"
214        workloads = [
215            "bitmanip/bitMisc.bin",
216            "coremark_rv64gc_o2/coremark-riscv64-xs.bin",
217            "coremark_rv64gc_o3/coremark-riscv64-xs.bin",
218            "coremark_rv64gcb_o3/coremark-riscv64-xs.bin",
219            "ext_intr/amtest-riscv64-xs.bin"
220        ]
221        misc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
222        return misc_tests
223
224    def __am_apps_path(self, bench):
225        filename = f"{bench}-riscv64-noop.bin"
226        return [os.path.join(self.args.am_home, "apps", bench, "build", filename)]
227
228    def __get_ci_workloads(self, name):
229        workloads = {
230            "linux-hello": "bbl.bin",
231            "povray": "_700480000000_.gz",
232            "mcf": "_17520000000_.gz",
233            "xalancbmk": "_266100000000_.gz",
234            "gcc": "_39720000000_.gz",
235            "namd": "_434640000000_.gz",
236            "milc": "_103620000000_.gz",
237            "lbm": "_140840000000_.gz",
238            "gromacs": "_275480000000_.gz"
239        }
240        return [os.path.join("/home/ci-runner/xsenv/workloads", name, workloads[name])]
241
242    def run_ci(self, test):
243        all_tests = {
244            "cputest": self.__get_ci_cputest,
245            "riscv-tests": self.__get_ci_rvtest,
246            "misc-tests": self.__get_ci_misc,
247            "microbench": self.__am_apps_path,
248            "coremark": self.__am_apps_path
249        }
250        for target in all_tests.get(test, self.__get_ci_workloads)(test):
251            print(target)
252            ret = self.run_emu(target)
253            if ret:
254                return ret
255        return 0
256
257if __name__ == "__main__":
258    parser = argparse.ArgumentParser(description='Python wrapper for XiangShan')
259    parser.add_argument('workload', nargs='?', type=str, default="",
260                        help='input workload file in binary format')
261    # actions
262    parser.add_argument('--build', action='store_true', help='build XS emu')
263    parser.add_argument('--generate', action='store_true', help='generate XS verilog')
264    parser.add_argument('--ci', nargs='?', type=str, const="", help='run CI tests')
265    # environment variables
266    parser.add_argument('--nemu', nargs='?', type=str, help='path to nemu')
267    parser.add_argument('--am', nargs='?', type=str, help='path to nexus-am')
268    parser.add_argument('--dramsim3', nargs='?', type=str, help='path to dramsim3')
269    parser.add_argument('--rvtest', nargs='?', type=str, help='path to riscv-tests')
270    # chisel arguments
271    parser.add_argument('--disable-log', action='store_true', help='disable log')
272    parser.add_argument('--dual-core', action='store_true', help='dual core')
273    # makefile arguments
274    parser.add_argument('--with-dramsim3', action='store_true', help='enable dramsim3')
275    parser.add_argument('--threads', nargs='?', type=int, help='number of emu threads')
276    parser.add_argument('--trace', action='store_true', help='enable waveform')
277    parser.add_argument('--config', nargs='?', type=str, help='config')
278    # emu arguments
279    parser.add_argument('--numa', action='store_true', help='use numactl')
280    parser.add_argument('--max-instr', nargs='?', type=int, help='max instr')
281
282    args = parser.parse_args()
283
284    xs = XiangShan(args)
285    ret = xs.run(args)
286
287    sys.exit(ret)
288