xref: /XiangShan/scripts/xiangshan.py (revision a273862e37f1d43bee748f2a6353320a2f52f6f4)
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
24import time
25
26
27class XSArgs(object):
28    script_path = os.path.realpath(__file__)
29    # default path to the repositories
30    noop_home = os.path.join(os.path.dirname(script_path), "..")
31    nemu_home = os.path.join(noop_home, "../NEMU")
32    am_home = os.path.join(noop_home, "../nexus-am")
33    dramsim3_home = os.path.join(noop_home, "../DRAMsim3")
34    rvtest_home = os.path.join(noop_home, "../riscv-tests")
35    default_wave_home = os.path.join(noop_home, "build")
36    wave_home   = default_wave_home
37
38    def __init__(self, args):
39        # all path environment variables that should be set
40        all_path = [
41            # (python argument, environment variable, default, target function)
42            (None, "NOOP_HOME", self.noop_home, self.set_noop_home),
43            (args.nemu, "NEMU_HOME", self.nemu_home, self.set_nemu_home),
44            (args.am, "AM_HOME", self.am_home, self.set_am_home),
45            (args.dramsim3, "DRAMSIM3_HOME", self.dramsim3_home, self.set_dramsim3_home),
46            (args.rvtest, "RVTEST_HOME", self.rvtest_home, self.set_rvtest_home),
47        ]
48        for (arg_in, env, default, set_func) in all_path:
49            set_func(self.__extract_path(arg_in, env, default))
50        # Chisel arguments
51        self.disable_log = args.disable_log
52        self.num_cores = args.num_cores
53        # Makefile arguments
54        self.threads = args.threads
55        self.with_dramsim3 = 1 if args.with_dramsim3 else None
56        self.trace = 1 if args.trace or not args.disable_fork  else None
57        self.config = args.config
58        # emu arguments
59        self.max_instr = args.max_instr
60        self.seed = random.randint(0, 9999)
61        self.numa = args.numa
62        self.diff = args.diff
63        self.fork = not args.disable_fork
64        # wave dump path
65        if args.wave_dump is not None:
66            self.set_wave_home(args.wave_dump)
67        else:
68            self.set_wave_home(self.default_wave_home)
69
70    def get_env_variables(self):
71        all_env = {
72            "NOOP_HOME"    : self.noop_home,
73            "NEMU_HOME"    : self.nemu_home,
74            "WAVE_HOME"    : self.wave_home,
75            "AM_HOME"      : self.am_home,
76            "DRAMSIM3_HOME": self.dramsim3_home
77        }
78        return all_env
79
80    def get_chisel_args(self, prefix=None):
81        chisel_args = [
82            (self.disable_log, "disable-log")
83        ]
84        args = map(lambda x: x[1], filter(lambda arg: arg[0], chisel_args))
85        if prefix is not None:
86            args = map(lambda x: prefix + x, args)
87        return args
88
89    def get_makefile_args(self):
90        makefile_args = [
91            (self.threads,       "EMU_THREADS"),
92            (self.with_dramsim3, "WITH_DRAMSIM3"),
93            (self.trace,         "EMU_TRACE"),
94            (self.config,        "CONFIG"),
95            (self.num_cores,     "NUM_CORES")
96        ]
97        args = filter(lambda arg: arg[0] is not None, makefile_args)
98        return args
99
100    def get_emu_args(self):
101        emu_args = [
102            (self.max_instr, "max-instr"),
103            (self.diff,      "diff"),
104            (self.seed,      "seed")
105        ]
106        args = filter(lambda arg: arg[0] is not None, emu_args)
107        return args
108
109    def show(self):
110        print("Extra environment variables:")
111        env = self.get_env_variables()
112        for env_name in env:
113            print(f"{env_name}: {env[env_name]}")
114        print()
115        print("Chisel arguments:")
116        print(" ".join(self.get_chisel_args()))
117        print()
118        print("Makefile arguments:")
119        for val, name in self.get_makefile_args():
120            print(f"{name}={val}")
121        print()
122        print("emu arguments:")
123        for val, name in self.get_emu_args():
124            print(f"--{name} {val}")
125        print()
126
127    def __extract_path(self, path, env=None, default=None):
128        if path is None and env is not None:
129            path = os.getenv(env)
130        if path is None and default is not None:
131            path = default
132        path = os.path.realpath(path)
133        return path
134
135    def set_noop_home(self, path):
136        self.noop_home = path
137
138    def set_nemu_home(self, path):
139        self.nemu_home = path
140
141    def set_am_home(self, path):
142        self.am_home = path
143
144    def set_dramsim3_home(self, path):
145        self.dramsim3_home = path
146
147    def set_rvtest_home(self, path):
148        self.rvtest_home = path
149
150    def set_wave_home(self, path):
151        print(f"set wave home to {path}")
152        self.wave_home = path
153
154# XiangShan environment
155class XiangShan(object):
156    def __init__(self, args):
157        self.args = XSArgs(args)
158
159    def show(self):
160        self.args.show()
161
162    def generate_verilog(self):
163        print("Generating XiangShan verilog with the following configurations:")
164        self.show()
165        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
166        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
167        return_code = self.__exec_cmd(f'make -C $NOOP_HOME verilog SIM_ARGS="{sim_args}" {make_args}')
168        return return_code
169
170    def build_emu(self):
171        print("Building XiangShan emu with the following configurations:")
172        self.show()
173        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
174        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
175        return_code = self.__exec_cmd(f'make -C $NOOP_HOME emu -j200 SIM_ARGS="{sim_args}" {make_args}')
176        return return_code
177
178    def run_emu(self, workload):
179        print("Running XiangShan emu with the following configurations:")
180        self.show()
181        emu_args = " ".join(map(lambda arg: f"--{arg[1]} {arg[0]}", self.args.get_emu_args()))
182        print("workload:", workload)
183        numa_args = f"numactl -m 1 -C 64-{64+self.args.threads-1}" if self.args.numa else ""
184        fork_args = "--enable-fork" if self.args.fork else ""
185        return_code = self.__exec_cmd(f'{numa_args} $NOOP_HOME/build/emu -i {workload} {emu_args} {fork_args}')
186        return return_code
187
188    def run(self, args):
189        if args.ci is not None:
190            return self.run_ci(args.ci)
191        actions = [
192            (args.generate, lambda _ : self.generate_verilog()),
193            (args.build, lambda _ : self.build_emu()),
194            (args.workload, lambda args: self.run_emu(args.workload))
195        ]
196        valid_actions = map(lambda act: act[1], filter(lambda act: act[0], actions))
197        for i, action in enumerate(valid_actions):
198            print(f"Action {i}:")
199            ret = action(args)
200            if ret:
201                return ret
202        return 0
203
204    def __exec_cmd(self, cmd):
205        env = dict(os.environ)
206        env.update(self.args.get_env_variables())
207        print("subprocess call cmd:", cmd)
208        start = time.time()
209        return_code = subprocess.call(cmd, shell=True, env=env)
210        end = time.time()
211        print(f"Elapsed time: {end - start} seconds")
212        return return_code
213
214    def __get_ci_cputest(self, name=None):
215        base_dir = os.path.join(self.args.am_home, "tests/cputest/build")
216        cputest = os.listdir(base_dir)
217        cputest = filter(lambda x: x.endswith(".bin"), cputest)
218        cputest = map(lambda x: os.path.join(base_dir, x), cputest)
219        return cputest
220
221    def __get_ci_rvtest(self, name=None):
222        base_dir = os.path.join(self.args.rvtest_home, "isa/build")
223        riscv_tests = os.listdir(base_dir)
224        riscv_tests = filter(lambda x: x.endswith(".bin"), riscv_tests)
225        all_rv_tests = ["rv64ui", "rv64um", "rv64ua", "rv64uf", "rv64ud"]
226        riscv_tests = filter(lambda x: x[:6] in all_rv_tests, riscv_tests)
227        riscv_tests = map(lambda x: os.path.join(base_dir, x), riscv_tests)
228        return riscv_tests
229
230    def __get_ci_misc(self, name=None):
231        base_dir = "/home/ci-runner/xsenv/workloads"
232        workloads = [
233            "bitmanip/bitMisc.bin",
234            "crypto/crypto-riscv64-noop.bin",
235            "coremark_rv64gc_o2/coremark-riscv64-xs.bin",
236            "coremark_rv64gc_o3/coremark-riscv64-xs.bin",
237            "coremark_rv64gcb_o3/coremark-riscv64-xs.bin",
238            "ext_intr/amtest-riscv64-xs.bin",
239            "cache-alias/aliastest-riscv64-xs.bin",
240            "Svinval/rv64mi-p-svinval.bin",
241            "pmp/pmp.riscv.bin",
242            "asid/asid.bin",
243            "cache-management/softprefetch-riscv64-noop.bin"
244        ]
245        misc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
246        return misc_tests
247
248    def __am_apps_path(self, bench):
249        filename = f"{bench}-riscv64-noop.bin"
250        return [os.path.join(self.args.am_home, "apps", bench, "build", filename)]
251
252    def __get_ci_workloads(self, name):
253        workloads = {
254            "linux-hello": "bbl.bin",
255            "povray": "_700480000000_.gz",
256            "mcf": "_17520000000_.gz",
257            "xalancbmk": "_266100000000_.gz",
258            "gcc": "_39720000000_.gz",
259            "namd": "_434640000000_.gz",
260            "milc": "_103620000000_.gz",
261            "lbm": "_140840000000_.gz",
262            "gromacs": "_275480000000_.gz",
263            "wrf": "_1916220000000_.gz",
264            "astar": "_122060000000_.gz"
265        }
266        return [os.path.join("/home/ci-runner/xsenv/workloads", name, workloads[name])]
267
268    def run_ci(self, test):
269        all_tests = {
270            "cputest": self.__get_ci_cputest,
271            "riscv-tests": self.__get_ci_rvtest,
272            "misc-tests": self.__get_ci_misc,
273            "microbench": self.__am_apps_path,
274            "coremark": self.__am_apps_path
275        }
276        for target in all_tests.get(test, self.__get_ci_workloads)(test):
277            print(target)
278            ret = self.run_emu(target)
279            if ret:
280                if self.args.default_wave_home != self.args.wave_home:
281                    print("copy wave file to " + self.args.wave_home)
282                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.vcd $WAVE_HOME")
283                    self.__exec_cmd(f"cp $NOOP_HOME/build/emu $WAVE_HOME")
284                    self.__exec_cmd(f"cp $NOOP_HOME/build/SimTop.v $WAVE_HOME")
285                return ret
286        return 0
287
288if __name__ == "__main__":
289    parser = argparse.ArgumentParser(description='Python wrapper for XiangShan')
290    parser.add_argument('workload', nargs='?', type=str, default="",
291                        help='input workload file in binary format')
292    # actions
293    parser.add_argument('--build', action='store_true', help='build XS emu')
294    parser.add_argument('--generate', action='store_true', help='generate XS verilog')
295    parser.add_argument('--ci', nargs='?', type=str, const="", help='run CI tests')
296    # environment variables
297    parser.add_argument('--nemu', nargs='?', type=str, help='path to nemu')
298    parser.add_argument('--am', nargs='?', type=str, help='path to nexus-am')
299    parser.add_argument('--dramsim3', nargs='?', type=str, help='path to dramsim3')
300    parser.add_argument('--rvtest', nargs='?', type=str, help='path to riscv-tests')
301    parser.add_argument('--wave-dump', nargs='?', type=str , help='path to dump wave')
302    # chisel arguments
303    parser.add_argument('--disable-log', action='store_true', help='disable log')
304    parser.add_argument('--num-cores', type=int, help='number of cores')
305    # makefile arguments
306    parser.add_argument('--with-dramsim3', action='store_true', help='enable dramsim3')
307    parser.add_argument('--threads', nargs='?', type=int, help='number of emu threads')
308    parser.add_argument('--trace', action='store_true', help='enable waveform')
309    parser.add_argument('--config', nargs='?', type=str, help='config')
310    # emu arguments
311    parser.add_argument('--numa', action='store_true', help='use numactl')
312    parser.add_argument('--diff', nargs='?', default="./ready-to-run/riscv64-nemu-interpreter-so", type=str, help='nemu so')
313    parser.add_argument('--max-instr', nargs='?', type=int, help='max instr')
314    parser.add_argument('--disable-fork', action='store_true', help='disable lightSSS')
315    # ci action head sha
316
317    args = parser.parse_args()
318
319    xs = XiangShan(args)
320    ret = xs.run(args)
321
322    sys.exit(ret)
323