xref: /XiangShan/scripts/xiangshan.py (revision 67ba96b4871c459c09df20e3052738174021a830)
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 json
21import os
22import random
23import signal
24import subprocess
25import sys
26import time
27
28import psutil
29
30
31def load_all_gcpt(gcpt_path, json_path):
32    all_gcpt = []
33    with open(json_path) as f:
34        data = json.load(f)
35    for benchspec in data:
36        for point in data[benchspec]:
37            weight = data[benchspec][point]
38            gcpt = os.path.join(gcpt_path, "_".join([benchspec, point, weight]))
39            bin_dir = os.path.join(gcpt, "0")
40            bin_file = list(os.listdir(bin_dir))
41            assert(len(bin_file) == 1)
42            bin_path = os.path.join(bin_dir, bin_file[0])
43            assert(os.path.isfile(bin_path))
44            all_gcpt.append(bin_path)
45    return all_gcpt
46
47class XSArgs(object):
48    script_path = os.path.realpath(__file__)
49    # default path to the repositories
50    noop_home = os.path.join(os.path.dirname(script_path), "..")
51    nemu_home = os.path.join(noop_home, "../NEMU")
52    am_home = os.path.join(noop_home, "../nexus-am")
53    dramsim3_home = os.path.join(noop_home, "../DRAMsim3")
54    rvtest_home = os.path.join(noop_home, "../riscv-tests")
55    default_wave_home = os.path.join(noop_home, "build")
56    wave_home   = default_wave_home
57
58    def __init__(self, args):
59        # all path environment variables that should be set
60        all_path = [
61            # (python argument, environment variable, default, target function)
62            (None, "NOOP_HOME", self.noop_home, self.set_noop_home),
63            (args.nemu, "NEMU_HOME", self.nemu_home, self.set_nemu_home),
64            (args.am, "AM_HOME", self.am_home, self.set_am_home),
65            (args.dramsim3, "DRAMSIM3_HOME", self.dramsim3_home, self.set_dramsim3_home),
66            (args.rvtest, "RVTEST_HOME", self.rvtest_home, self.set_rvtest_home),
67        ]
68        for (arg_in, env, default, set_func) in all_path:
69            set_func(self.__extract_path(arg_in, env, default))
70        # Chisel arguments
71        self.enable_log = args.enable_log
72        self.num_cores = args.num_cores
73        # Makefile arguments
74        self.threads = args.threads
75        self.with_dramsim3 = 1 if args.with_dramsim3 else None
76        self.is_release = 1 if args.release else None
77        self.is_spike = "spike" if args.spike else None
78        self.trace = 1 if args.trace or not args.disable_fork and not args.trace_fst else None
79        self.trace_fst = "fst" if args.trace_fst else None
80        self.config = args.config
81        # emu arguments
82        self.max_instr = args.max_instr
83        self.ram_size = args.ram_size
84        self.seed = random.randint(0, 9999)
85        self.numa = args.numa
86        self.diff = args.diff
87        if args.spike and "nemu" in args.diff:
88            self.diff = self.diff.replace("nemu-interpreter", "spike")
89        self.fork = not args.disable_fork
90        self.disable_diff = args.no_diff
91        # wave dump path
92        if args.wave_dump is not None:
93            self.set_wave_home(args.wave_dump)
94        else:
95            self.set_wave_home(self.default_wave_home)
96
97    def get_env_variables(self):
98        all_env = {
99            "NOOP_HOME"    : self.noop_home,
100            "NEMU_HOME"    : self.nemu_home,
101            "WAVE_HOME"    : self.wave_home,
102            "AM_HOME"      : self.am_home,
103            "DRAMSIM3_HOME": self.dramsim3_home,
104            "MODULEPATH": "/usr/share/Modules/modulefiles:/etc/modulefiles"
105        }
106        return all_env
107
108    def get_chisel_args(self, prefix=None):
109        chisel_args = [
110            (self.enable_log, "enable-log")
111        ]
112        args = map(lambda x: x[1], filter(lambda arg: arg[0], chisel_args))
113        if prefix is not None:
114            args = map(lambda x: prefix + x, args)
115        return args
116
117    def get_makefile_args(self):
118        makefile_args = [
119            (self.threads,       "EMU_THREADS"),
120            (self.with_dramsim3, "WITH_DRAMSIM3"),
121            (self.is_release,    "RELEASE"),
122            (self.is_spike,      "REF"),
123            (self.trace,         "EMU_TRACE"),
124            (self.trace_fst,     "EMU_TRACE"),
125            (self.config,        "CONFIG"),
126            (self.num_cores,     "NUM_CORES")
127        ]
128        args = filter(lambda arg: arg[0] is not None, makefile_args)
129        return args
130
131    def get_emu_args(self):
132        emu_args = [
133            (self.max_instr, "max-instr"),
134            (self.diff,      "diff"),
135            (self.seed,      "seed"),
136            (self.ram_size,  "ram-size"),
137        ]
138        args = filter(lambda arg: arg[0] is not None, emu_args)
139        return args
140
141    def show(self):
142        print("Extra environment variables:")
143        env = self.get_env_variables()
144        for env_name in env:
145            print(f"{env_name}: {env[env_name]}")
146        print()
147        print("Chisel arguments:")
148        print(" ".join(self.get_chisel_args()))
149        print()
150        print("Makefile arguments:")
151        for val, name in self.get_makefile_args():
152            print(f"{name}={val}")
153        print()
154        print("emu arguments:")
155        for val, name in self.get_emu_args():
156            print(f"--{name} {val}")
157        print()
158
159    def __extract_path(self, path, env=None, default=None):
160        if path is None and env is not None:
161            path = os.getenv(env)
162        if path is None and default is not None:
163            path = default
164        path = os.path.realpath(path)
165        return path
166
167    def set_noop_home(self, path):
168        self.noop_home = path
169
170    def set_nemu_home(self, path):
171        self.nemu_home = path
172
173    def set_am_home(self, path):
174        self.am_home = path
175
176    def set_dramsim3_home(self, path):
177        self.dramsim3_home = path
178
179    def set_rvtest_home(self, path):
180        self.rvtest_home = path
181
182    def set_wave_home(self, path):
183        print(f"set wave home to {path}")
184        self.wave_home = path
185
186# XiangShan environment
187class XiangShan(object):
188    def __init__(self, args):
189        self.args = XSArgs(args)
190        self.timeout = args.timeout
191
192    def show(self):
193        self.args.show()
194
195    def make_clean(self):
196        print("Clean up CI workspace")
197        self.show()
198        return_code = self.__exec_cmd(f'make -C $NOOP_HOME clean')
199        return return_code
200
201    def generate_verilog(self):
202        print("Generating XiangShan verilog with the following configurations:")
203        self.show()
204        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
205        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
206        return_code = self.__exec_cmd(f'make -C $NOOP_HOME verilog SIM_ARGS="{sim_args}" {make_args}')
207        return return_code
208
209    def generate_sim_verilog(self):
210        print("Generating XiangShan sim-verilog with the following configurations:")
211        self.show()
212        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
213        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
214        return_code = self.__exec_cmd(f'make -C $NOOP_HOME sim-verilog SIM_ARGS="{sim_args}" {make_args}')
215        return return_code
216
217    def build_emu(self):
218        print("Building XiangShan emu with the following configurations:")
219        self.show()
220        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
221        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
222        return_code = self.__exec_cmd(f'make -C $NOOP_HOME emu -j200 SIM_ARGS="{sim_args}" {make_args}')
223        return return_code
224
225    def build_simv(self):
226        print("Building XiangShan simv with the following configurations")
227        self.show()
228        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
229        # TODO: make the following commands grouped as unseen scripts
230        return_code = self.__exec_cmd(f'\
231            eval `/usr/bin/modulecmd zsh load license`;\
232            eval `/usr/bin/modulecmd zsh load synopsys/vcs/Q-2020.03-SP2`;\
233            eval `/usr/bin/modulecmd zsh load synopsys/verdi/S-2021.09-SP1`;\
234            VERDI_HOME=/nfs/tools/synopsys/verdi/S-2021.09-SP1 \
235            make -C $NOOP_HOME simv {make_args} CONSIDER_FSDB=1')  # set CONSIDER_FSDB for compatibility
236        return return_code
237
238    def run_emu(self, workload):
239        print("Running XiangShan emu with the following configurations:")
240        self.show()
241        emu_args = " ".join(map(lambda arg: f"--{arg[1]} {arg[0]}", self.args.get_emu_args()))
242        print("workload:", workload)
243        numa_args = ""
244        if self.args.numa:
245            numa_info = get_free_cores(self.args.threads)
246            numa_args = f"numactl -m {numa_info[0]} -C {numa_info[1]}-{numa_info[2]}"
247        fork_args = "--enable-fork" if self.args.fork else ""
248        diff_args = "--no-diff" if self.args.disable_diff else ""
249        return_code = self.__exec_cmd(f'{numa_args} $NOOP_HOME/build/emu -i {workload} {emu_args} {fork_args} {diff_args}')
250        return return_code
251
252    def run_simv(self, workload):
253        print("Running XiangShan simv with the following configurations:")
254        self.show()
255        diff_args = "$NOOP_HOME/"+ args.diff
256        return_code = self.__exec_cmd(f'$NOOP_HOME/difftest/simv +workload={workload} +diff={diff_args}')
257        return return_code
258
259    def run(self, args):
260        if args.ci is not None:
261            return self.run_ci(args.ci)
262        if args.ci_vcs is not None:
263            return self.run_ci_vcs(args.ci_vcs)
264        actions = [
265            (args.generate, lambda _ : self.generate_verilog()),
266            (args.vcs_gen, lambda _ : self.generate_sim_verilog()),
267            (args.build, lambda _ : self.build_emu()),
268            (args.vcs_build, lambda _ : self.build_simv()),
269            (args.workload, lambda args: self.run_emu(args.workload)),
270            (args.clean, lambda _ : self.make_clean())
271        ]
272        valid_actions = map(lambda act: act[1], filter(lambda act: act[0], actions))
273        for i, action in enumerate(valid_actions):
274            print(f"Action {i}:")
275            ret = action(args)
276            if ret:
277                return ret
278        return 0
279
280    def __exec_cmd(self, cmd):
281        env = dict(os.environ)
282        env.update(self.args.get_env_variables())
283        print("subprocess call cmd:", cmd)
284        start = time.time()
285        proc = subprocess.Popen(cmd, shell=True, env=env, preexec_fn=os.setsid)
286        try:
287            return_code = proc.wait(self.timeout)
288            end = time.time()
289            print(f"Elapsed time: {end - start} seconds")
290            return return_code
291        except (KeyboardInterrupt, subprocess.TimeoutExpired):
292            os.killpg(os.getpgid(proc.pid), signal.SIGINT)
293            print(f"KeyboardInterrupt or TimeoutExpired.")
294            return 0
295
296    def __get_ci_cputest(self, name=None):
297        base_dir = os.path.join(self.args.am_home, "tests/cputest/build")
298        cputest = os.listdir(base_dir)
299        cputest = filter(lambda x: x.endswith(".bin"), cputest)
300        cputest = map(lambda x: os.path.join(base_dir, x), cputest)
301        return cputest
302
303    def __get_ci_rvtest(self, name=None):
304        base_dir = os.path.join(self.args.rvtest_home, "isa/build")
305        riscv_tests = os.listdir(base_dir)
306        riscv_tests = filter(lambda x: x.endswith(".bin"), riscv_tests)
307        all_rv_tests = ["rv64ui", "rv64um", "rv64ua", "rv64uf", "rv64ud"]
308        riscv_tests = filter(lambda x: x[:6] in all_rv_tests, riscv_tests)
309        riscv_tests = map(lambda x: os.path.join(base_dir, x), riscv_tests)
310        return riscv_tests
311
312    def __get_ci_misc(self, name=None):
313        base_dir = "/nfs/home/share/ci-workloads"
314        workloads = [
315            "bitmanip/bitMisc.bin",
316            "crypto/crypto-riscv64-noop.bin",
317            "coremark_rv64gc_o2/coremark-riscv64-xs.bin",
318            "coremark_rv64gc_o3/coremark-riscv64-xs.bin",
319            "coremark_rv64gcb_o3/coremark-riscv64-xs.bin",
320            "ext_intr/amtest-riscv64-xs.bin",
321            "cache-alias/aliastest-riscv64-xs.bin",
322            "Svinval/rv64mi-p-svinval.bin",
323            "pmp/pmp.riscv.bin",
324            "pmp/pmp-am/amtest-riscv64-xs.bin",
325            "pmp/hugepage-pmp-atom/amtest-riscv64-xs.bin",
326            "asid/asid.bin",
327            "isa_misc/xret_clear_mprv.bin",
328            "isa_misc/satp_ppn.bin",
329            "cache-management/softprefetchtest-riscv64-xs.bin"
330        ]
331        misc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
332        return misc_tests
333
334    def __get_ci_mc(self, name=None):
335        base_dir = "/nfs/home/share/ci-workloads"
336        workloads = [
337            "dualcoretest/ldvio-riscv64-xs.bin"
338        ]
339        mc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
340        return mc_tests
341
342    def __get_ci_nodiff(self, name=None):
343        base_dir = "/nfs/home/share/ci-workloads"
344        workloads = [
345            "cache-management/cacheoptest-riscv64-xs.bin"
346        ]
347        tests = map(lambda x: os.path.join(base_dir, x), workloads)
348        return tests
349
350    def __am_apps_path(self, bench):
351        filename = f"{bench}-riscv64-noop.bin"
352        return [os.path.join(self.args.am_home, "apps", bench, "build", filename)]
353
354    def __get_ci_workloads(self, name):
355        workloads = {
356            "linux-hello": "bbl.bin",
357            "linux-hello-smp": "bbl.bin",
358            "povray": "_700480000000_.gz",
359            "mcf": "_17520000000_.gz",
360            "xalancbmk": "_266100000000_.gz",
361            "gcc": "_39720000000_.gz",
362            "namd": "_434640000000_.gz",
363            "milc": "_103620000000_.gz",
364            "lbm": "_140840000000_.gz",
365            "gromacs": "_275480000000_.gz",
366            "wrf": "_1916220000000_.gz",
367            "astar": "_122060000000_.gz"
368        }
369        if name in workloads:
370            return [os.path.join("/nfs/home/share/ci-workloads", name, workloads[name])]
371        # select a random SPEC checkpoint
372        assert(name == "random")
373        all_cpt = [
374            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o2_20m/take_cpt",
375            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o3_20m/take_cpt",
376            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_20m/take_cpt",
377            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_50m/take_cpt",
378            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o2_20m/take_cpt",
379            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o3_20m/take_cpt",
380            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gc_o2_50m/take_cpt",
381            "/nfs-nvme/home/share/checkpoints_profiles/spec17_speed_rv64gcb_o3_20m/take_cpt"
382        ]
383        all_json = [
384            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o2_20m/json/simpoint_summary.json",
385            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o3_20m/simpoint_summary.json",
386            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_20m/simpoint_summary.json",
387            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_50m/simpoint_summary.json",
388            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o2_20m/simpoint_summary.json",
389            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o3_20m/simpoint_summary.json",
390            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gc_o2_50m/simpoint_summary.json",
391            "/nfs-nvme/home/share/checkpoints_profiles/spec17_speed_rv64gcb_o3_20m/simpoint_summary.json"
392        ]
393        assert(len(all_cpt) == len(all_json))
394        cpt_path, json_path = random.choice(list(zip(all_cpt, all_json)))
395        all_gcpt = load_all_gcpt(cpt_path, json_path)
396        return [random.choice(all_gcpt)]
397
398    def run_ci(self, test):
399        all_tests = {
400            "cputest": self.__get_ci_cputest,
401            "riscv-tests": self.__get_ci_rvtest,
402            "misc-tests": self.__get_ci_misc,
403            "mc-tests": self.__get_ci_mc,
404            "nodiff-tests": self.__get_ci_nodiff,
405            "microbench": self.__am_apps_path,
406            "coremark": self.__am_apps_path
407        }
408        for target in all_tests.get(test, self.__get_ci_workloads)(test):
409            print(target)
410            ret = self.run_emu(target)
411            if ret:
412                if self.args.default_wave_home != self.args.wave_home:
413                    print("copy wave file to " + self.args.wave_home)
414                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.vcd $WAVE_HOME")
415                    self.__exec_cmd(f"cp $NOOP_HOME/build/emu $WAVE_HOME")
416                    self.__exec_cmd(f"cp $NOOP_HOME/build/SimTop.v $WAVE_HOME")
417                return ret
418        return 0
419
420    def run_ci_vcs(self, test):
421        all_tests = {
422            "cputest": self.__get_ci_cputest,
423            "riscv-tests": self.__get_ci_rvtest,
424            "misc-tests": self.__get_ci_misc,
425            "mc-tests": self.__get_ci_mc,
426            "nodiff-tests": self.__get_ci_nodiff,
427            "microbench": self.__am_apps_path,
428            "coremark": self.__am_apps_path
429        }
430        for target in all_tests.get(test, self.__get_ci_workloads)(test):
431            print(target)
432            ret = self.run_simv(target)
433            if ret:
434                if self.args.default_wave_home != self.args.wave_home:
435                    print("copy wave file to " + self.args.wave_home)
436                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.vcd $WAVE_HOME")
437                    self.__exec_cmd(f"cp $NOOP_HOME/build/emu $WAVE_HOME")
438                    self.__exec_cmd(f"cp $NOOP_HOME/build/SimTop.v $WAVE_HOME")
439                return ret
440        return 0
441
442def get_free_cores(n):
443    while True:
444        # To avoid potential conflicts, we allow CI to use SMT.
445        num_logical_core = psutil.cpu_count(logical=False)
446        core_usage = psutil.cpu_percent(interval=1, percpu=True)
447        num_window = num_logical_core // n
448        for i in range(num_window):
449            window_usage = core_usage[i * n : i * n + n]
450            if sum(window_usage) < 30 * n and True not in map(lambda x: x > 90, window_usage):
451                return (((i * n) % 128)// 64, i * n, i * n + n - 1)
452        print(f"No free {n} cores found. CPU usage: {core_usage}\n")
453
454if __name__ == "__main__":
455    parser = argparse.ArgumentParser(description='Python wrapper for XiangShan')
456    parser.add_argument('workload', nargs='?', type=str, default="",
457                        help='input workload file in binary format')
458    # actions
459    parser.add_argument('--build', action='store_true', help='build XS emu')
460    parser.add_argument('--generate', action='store_true', help='generate XS verilog')
461    parser.add_argument('--vcs-gen', action='store_true', help='generate XS sim verilog for vcs')
462    parser.add_argument('--vcs-build', action='store_true', help='build XS simv')
463    parser.add_argument('--ci', nargs='?', type=str, const="", help='run CI tests')
464    parser.add_argument('--ci-vcs', nargs='?', type=str, const="", help='run CI tests on simv')
465    parser.add_argument('--clean', action='store_true', help='clean up XiangShan CI workspace')
466    parser.add_argument('--timeout', nargs='?', type=int, default=None, help='timeout (in seconds)')
467    # environment variables
468    parser.add_argument('--nemu', nargs='?', type=str, help='path to nemu')
469    parser.add_argument('--am', nargs='?', type=str, help='path to nexus-am')
470    parser.add_argument('--dramsim3', nargs='?', type=str, help='path to dramsim3')
471    parser.add_argument('--rvtest', nargs='?', type=str, help='path to riscv-tests')
472    parser.add_argument('--wave-dump', nargs='?', type=str , help='path to dump wave')
473    # chisel arguments
474    parser.add_argument('--enable-log', action='store_true', help='enable log')
475    parser.add_argument('--num-cores', type=int, help='number of cores')
476    # makefile arguments
477    parser.add_argument('--release', action='store_true', help='enable release')
478    parser.add_argument('--spike', action='store_true', help='enable spike diff')
479    parser.add_argument('--with-dramsim3', action='store_true', help='enable dramsim3')
480    parser.add_argument('--threads', nargs='?', type=int, help='number of emu threads')
481    parser.add_argument('--trace', action='store_true', help='enable vcd waveform')
482    parser.add_argument('--trace-fst', action='store_true', help='enable fst waveform')
483    parser.add_argument('--config', nargs='?', type=str, help='config')
484    # emu arguments
485    parser.add_argument('--numa', action='store_true', help='use numactl')
486    parser.add_argument('--diff', nargs='?', default="./ready-to-run/riscv64-nemu-interpreter-so", type=str, help='nemu so')
487    parser.add_argument('--max-instr', nargs='?', type=int, help='max instr')
488    parser.add_argument('--disable-fork', action='store_true', help='disable lightSSS')
489    parser.add_argument('--no-diff', action='store_true', help='disable difftest')
490    parser.add_argument('--ram-size', nargs='?', type=str, help='manually set simulation memory size (8GB by default)')
491
492    args = parser.parse_args()
493
494    xs = XiangShan(args)
495    ret = xs.run(args)
496
497    sys.exit(ret)
498