xref: /XiangShan/scripts/xiangshan.py (revision 491c16ade93d4956fec6dde187943d72bb010bc4)
1#***************************************************************************************
2# Copyright (c) 2024 Beijing Institute of Open Source Chip (BOSC)
3# Copyright (c) 2020-2024 Institute of Computing Technology, Chinese Academy of Sciences
4# Copyright (c) 2020-2021 Peng Cheng Laboratory
5#
6# XiangShan is licensed under Mulan PSL v2.
7# You can use this software according to the terms and conditions of the Mulan PSL v2.
8# You may obtain a copy of Mulan PSL v2 at:
9#          http://license.coscl.org.cn/MulanPSL2
10#
11# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
12# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
13# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
14#
15# See the Mulan PSL v2 for more details.
16#***************************************************************************************
17
18# Simple version of xiangshan python wrapper
19
20import argparse
21import json
22import os
23import random
24import signal
25import subprocess
26import sys
27import time
28import shlex
29import psutil
30import re
31
32def find_files_with_suffix(root_dir, suffixes):
33    matching_files = []
34    for dirpath, _, filenames in os.walk(root_dir):
35        for filename in filenames:
36            if any(filename.endswith(suffix) for suffix in suffixes):
37                absolute_path = os.path.join(dirpath, filename)
38                matching_files.append(absolute_path)
39    return matching_files
40
41def load_all_gcpt(gcpt_paths):
42    all_gcpt = []
43    for gcpt_path in gcpt_paths:
44        all_gcpt.extend(find_files_with_suffix(gcpt_path, ['.zstd', '.gz']))
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        self.emu_optimize = args.emu_optimize
82        self.xprop = 1 if args.xprop else None
83        self.with_chiseldb = 0 if args.no_db else 1
84        # emu arguments
85        self.max_instr = args.max_instr
86        self.ram_size = args.ram_size
87        self.seed = random.randint(0, 9999)
88        self.numa = args.numa
89        self.diff = args.diff
90        if args.spike and "nemu" in args.diff:
91            self.diff = self.diff.replace("nemu-interpreter", "spike")
92        self.fork = not args.disable_fork
93        self.disable_diff = args.no_diff
94        self.disable_db = args.no_db
95        self.gcpt_restore_bin = args.gcpt_restore_bin
96        self.pgo = args.pgo
97        self.pgo_max_cycle = args.pgo_max_cycle
98        self.pgo_emu_args = args.pgo_emu_args
99        self.llvm_profdata = args.llvm_profdata
100        # wave dump path
101        if args.wave_dump is not None:
102            self.set_wave_home(args.wave_dump)
103        else:
104            self.set_wave_home(self.default_wave_home)
105
106    def get_env_variables(self):
107        all_env = {
108            "NOOP_HOME"    : self.noop_home,
109            "NEMU_HOME"    : self.nemu_home,
110            "WAVE_HOME"    : self.wave_home,
111            "AM_HOME"      : self.am_home,
112            "DRAMSIM3_HOME": self.dramsim3_home,
113            "MODULEPATH": "/usr/share/Modules/modulefiles:/etc/modulefiles"
114        }
115        return all_env
116
117    def get_chisel_args(self, prefix=None):
118        chisel_args = [
119            (self.enable_log, "enable-log")
120        ]
121        args = map(lambda x: x[1], filter(lambda arg: arg[0], chisel_args))
122        if prefix is not None:
123            args = map(lambda x: prefix + x, args)
124        return args
125
126    def get_makefile_args(self):
127        makefile_args = [
128            (self.threads,       "EMU_THREADS"),
129            (self.with_dramsim3, "WITH_DRAMSIM3"),
130            (self.is_release,    "RELEASE"),
131            (self.is_spike,      "REF"),
132            (self.trace,         "EMU_TRACE"),
133            (self.trace_fst,     "EMU_TRACE"),
134            (self.config,        "CONFIG"),
135            (self.num_cores,     "NUM_CORES"),
136            (self.emu_optimize,  "EMU_OPTIMIZE"),
137            (self.xprop,         "ENABLE_XPROP"),
138            (self.with_chiseldb, "WITH_CHISELDB"),
139            (self.pgo,           "PGO_WORKLOAD"),
140            (self.pgo_max_cycle, "PGO_MAX_CYCLE"),
141            (self.pgo_emu_args,  "PGO_EMU_ARGS"),
142            (self.llvm_profdata, "LLVM_PROFDATA"),
143        ]
144        args = filter(lambda arg: arg[0] is not None, makefile_args)
145        args = [(shlex.quote(str(arg[0])), arg[1]) for arg in args] # shell escape
146        return args
147
148    def get_emu_args(self):
149        emu_args = [
150            (self.max_instr, "max-instr"),
151            (self.diff,      "diff"),
152            (self.seed,      "seed"),
153            (self.ram_size,  "ram-size"),
154        ]
155        args = filter(lambda arg: arg[0] is not None, emu_args)
156        return args
157
158    def show(self):
159        print("Extra environment variables:")
160        env = self.get_env_variables()
161        for env_name in env:
162            print(f"{env_name}: {env[env_name]}")
163        print()
164        print("Chisel arguments:")
165        print(" ".join(self.get_chisel_args()))
166        print()
167        print("Makefile arguments:")
168        for val, name in self.get_makefile_args():
169            print(f"{name}={val}")
170        print()
171        print("emu arguments:")
172        for val, name in self.get_emu_args():
173            print(f"--{name} {val}")
174        print()
175
176    def __extract_path(self, path, env=None, default=None):
177        if path is None and env is not None:
178            path = os.getenv(env)
179        if path is None and default is not None:
180            path = default
181        path = os.path.realpath(path)
182        return path
183
184    def set_noop_home(self, path):
185        self.noop_home = path
186
187    def set_nemu_home(self, path):
188        self.nemu_home = path
189
190    def set_am_home(self, path):
191        self.am_home = path
192
193    def set_dramsim3_home(self, path):
194        self.dramsim3_home = path
195
196    def set_rvtest_home(self, path):
197        self.rvtest_home = path
198
199    def set_wave_home(self, path):
200        print(f"set wave home to {path}")
201        self.wave_home = path
202
203# XiangShan environment
204class XiangShan(object):
205    def __init__(self, args):
206        self.args = XSArgs(args)
207        self.timeout = args.timeout
208
209    def show(self):
210        self.args.show()
211
212    def make_clean(self):
213        print("Clean up CI workspace")
214        self.show()
215        return_code = self.__exec_cmd(f'make -C $NOOP_HOME clean')
216        return return_code
217
218    def generate_verilog(self):
219        print("Generating XiangShan verilog with the following configurations:")
220        self.show()
221        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
222        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
223        return_code = self.__exec_cmd(f'make -C $NOOP_HOME verilog SIM_ARGS="{sim_args}" {make_args}')
224        return return_code
225
226    def generate_sim_verilog(self):
227        print("Generating XiangShan sim-verilog with the following configurations:")
228        self.show()
229        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
230        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
231        return_code = self.__exec_cmd(f'make -C $NOOP_HOME sim-verilog SIM_ARGS="{sim_args}" {make_args}')
232        return return_code
233
234    def build_emu(self):
235        print("Building XiangShan emu with the following configurations:")
236        self.show()
237        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
238        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
239        return_code = self.__exec_cmd(f'make -C $NOOP_HOME emu -j200 SIM_ARGS="{sim_args}" {make_args}')
240        return return_code
241
242    def build_simv(self):
243        print("Building XiangShan simv with the following configurations")
244        self.show()
245        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
246        # TODO: make the following commands grouped as unseen scripts
247        return_code = self.__exec_cmd(f'\
248            eval `/usr/bin/modulecmd zsh load license`;\
249            eval `/usr/bin/modulecmd zsh load synopsys/vcs/Q-2020.03-SP2`;\
250            eval `/usr/bin/modulecmd zsh load synopsys/verdi/S-2021.09-SP1`;\
251            VERDI_HOME=/nfs/tools/synopsys/verdi/S-2021.09-SP1 \
252            make -C $NOOP_HOME simv {make_args} CONSIDER_FSDB=1')  # set CONSIDER_FSDB for compatibility
253        return return_code
254
255    def run_emu(self, workload):
256        print("Running XiangShan emu with the following configurations:")
257        self.show()
258        emu_args = " ".join(map(lambda arg: f"--{arg[1]} {arg[0]}", self.args.get_emu_args()))
259        print("workload:", workload)
260        numa_args = ""
261        if self.args.numa:
262            numa_info = get_free_cores(self.args.threads)
263            numa_args = f"numactl -m {numa_info[0]} -C {numa_info[1]}-{numa_info[2]}"
264        fork_args = "--enable-fork" if self.args.fork else ""
265        diff_args = "--no-diff" if self.args.disable_diff else ""
266        chiseldb_args = "--dump-db" if not self.args.disable_db else ""
267        gcpt_restore_args = f"-r {self.args.gcpt_restore_bin}" if len(self.args.gcpt_restore_bin) != 0 else ""
268        return_code = self.__exec_cmd(f'ulimit -s {32 * 1024}; {numa_args} $NOOP_HOME/build/emu -i {workload} {emu_args} {fork_args} {diff_args} {chiseldb_args} {gcpt_restore_args}')
269        return return_code
270
271    def run_simv(self, workload):
272        print("Running XiangShan simv with the following configurations:")
273        self.show()
274        diff_args = "$NOOP_HOME/"+ args.diff
275        assert_args = "-assert finish_maxfail=30 -assert global_finish_maxfail=10000"
276        return_code = self.__exec_cmd(f'cd $NOOP_HOME/build && ./simv +workload={workload} +diff={diff_args} +dump-wave=fsdb {assert_args} | tee simv.log')
277        with open(f"{self.args.noop_home}/build/simv.log") as f:
278            content = f.read()
279            if "Offending" in content or "HIT GOOD TRAP" not in content:
280                return 1
281        return return_code
282
283    def run(self, args):
284        if args.ci is not None:
285            return self.run_ci(args.ci)
286        if args.ci_vcs is not None:
287            return self.run_ci_vcs(args.ci_vcs)
288        actions = [
289            (args.generate, lambda _ : self.generate_verilog()),
290            (args.vcs_gen, lambda _ : self.generate_sim_verilog()),
291            (args.build, lambda _ : self.build_emu()),
292            (args.vcs_build, lambda _ : self.build_simv()),
293            (args.workload, lambda args: self.run_emu(args.workload)),
294            (args.clean, lambda _ : self.make_clean())
295        ]
296        valid_actions = map(lambda act: act[1], filter(lambda act: act[0], actions))
297        for i, action in enumerate(valid_actions):
298            print(f"Action {i}:")
299            ret = action(args)
300            if ret:
301                return ret
302        return 0
303
304    def __exec_cmd(self, cmd):
305        env = dict(os.environ)
306        env.update(self.args.get_env_variables())
307        print("subprocess call cmd:", cmd)
308        start = time.time()
309        proc = subprocess.Popen(cmd, shell=True, env=env, preexec_fn=os.setsid)
310        try:
311            return_code = proc.wait(self.timeout)
312            end = time.time()
313            print(f"Elapsed time: {end - start} seconds")
314            return return_code
315        except (KeyboardInterrupt, subprocess.TimeoutExpired):
316            os.killpg(os.getpgid(proc.pid), signal.SIGINT)
317            print(f"KeyboardInterrupt or TimeoutExpired.")
318            return 0
319
320    def __get_ci_cputest(self, name=None):
321        # base_dir = os.path.join(self.args.am_home, "tests/cputest/build")
322        base_dir = "/nfs/home/share/ci-workloads/nexus-am-workloads/tests/cputest"
323        cputest = os.listdir(base_dir)
324        cputest = filter(lambda x: x.endswith(".bin"), cputest)
325        cputest = map(lambda x: os.path.join(base_dir, x), cputest)
326        return cputest
327
328    def __get_ci_rvtest(self, name=None):
329        base_dir = os.path.join(self.args.rvtest_home, "isa/build")
330        riscv_tests = os.listdir(base_dir)
331        riscv_tests = filter(lambda x: x.endswith(".bin"), riscv_tests)
332        all_rv_tests = ["rv64ui", "rv64um", "rv64ua", "rv64uf", "rv64ud", "rv64mi"]
333        riscv_tests = filter(lambda x: x[:6] in all_rv_tests, riscv_tests)
334        riscv_tests = map(lambda x: os.path.join(base_dir, x), riscv_tests)
335        return riscv_tests
336
337    def __get_ci_misc(self, name=None):
338        base_dir = "/nfs/home/share/ci-workloads"
339        workloads = [
340            "bitmanip/bitMisc.bin",
341            "crypto/crypto-riscv64-noop.bin",
342            # "coremark_rv64gc_o2/coremark-riscv64-xs.bin",
343            # "coremark_rv64gc_o3/coremark-riscv64-xs.bin",
344            # "coremark_rv64gcb_o3/coremark-riscv64-xs.bin",
345            "nexus-am-workloads/amtest/external_intr-riscv64-xs.bin",
346            "nexus-am-workloads/tests/aliastest/aliastest-riscv64-xs.bin",
347            "Svinval/rv64mi-p-svinval.bin",
348            "pmp/pmp.riscv.bin",
349            "nexus-am-workloads/amtest/pmp_test-riscv64-xs.bin",
350            "nexus-am-workloads/amtest/sv39_hp_atom_test-riscv64-xs.bin",
351            "asid/asid.bin",
352            "isa_misc/xret_clear_mprv.bin",
353            "isa_misc/satp_ppn.bin",
354            "cache-management/softprefetchtest-riscv64-xs.bin",
355            "smstateen/rvh_test.bin",
356            "zacas/zacas-riscv64-xs.bin",
357            "Svpbmt/rvh_test.bin",
358            "Svnapot/svnapot-test.bin"
359        ]
360        misc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
361        return misc_tests
362
363    def __get_ci_rvhtest(self, name=None):
364        base_dir = "/nfs/home/share/ci-workloads/H-extension-tests"
365        workloads = [
366            "riscv-hyp-tests/rvh_test.bin",
367            "xvisor_wboxtest/checkpoint.gz",
368            "pointer-masking-test/M_HS_test/rvh_test.bin",
369            "pointer-masking-test/U_test/hint_UMode_hupmm2/rvh_test.bin",
370            "pointer-masking-test/U_test/vu_senvcfgpmm2/rvh_test.bin"
371        ]
372        rvh_tests = map(lambda x: os.path.join(base_dir, x), workloads)
373        return rvh_tests
374
375    def __get_ci_rvvbench(self, name=None):
376        base_dir = "/nfs/home/share/ci-workloads"
377        workloads = [
378            "rvv-bench/poly1305.bin",
379            "rvv-bench/mergelines.bin"
380        ]
381        rvvbench = map(lambda x: os.path.join(base_dir, x), workloads)
382        return rvvbench
383
384    def __get_ci_rvvtest(self, name=None):
385        base_dir = "/nfs/home/share/ci-workloads/V-extension-tests"
386        workloads = [
387            "rvv-test/vluxei32.v-0.bin",
388            "rvv-test/vlsseg4e32.v-0.bin",
389            "rvv-test/vlseg4e32.v-0.bin",
390            "rvv-test/vsetvl-0.bin",
391            "rvv-test/vsetivli-0.bin",
392            "rvv-test/vsuxei32.v-0.bin",
393            "rvv-test/vse16.v-0.bin",
394            "rvv-test/vsse16.v-1.bin",
395            "rvv-test/vlse32.v-0.bin",
396            "rvv-test/vsetvli-0.bin",
397            "rvv-test/vle16.v-0.bin",
398            "rvv-test/vle32.v-0.bin",
399            "rvv-test/vfsgnj.vv-0.bin",
400            "rvv-test/vfadd.vf-0.bin",
401            "rvv-test/vfsub.vf-0.bin",
402            "rvv-test/vslide1down.vx-0.bin"
403        ]
404        rvv_test = map(lambda x: os.path.join(base_dir, x), workloads)
405        return rvv_test
406
407    def __get_ci_F16test(self, name=None):
408        base_dir = "/nfs/home/share/ci-workloads/vector/F16-tests/build"
409        workloads = [
410            "rv64uzfhmin-p-fzfhmincvt.bin",
411            "rv64uzfh-p-fadd.bin",
412            "rv64uzfh-p-fclass.bin",
413            "rv64uzfh-p-fcmp.bin",
414            "rv64uzfh-p-fcvt.bin",
415            "rv64uzfh-p-fcvt_w.bin",
416            "rv64uzfh-p-fdiv.bin",
417            "rv64uzfh-p-fmadd.bin",
418            "rv64uzfh-p-fmin.bin",
419            "rv64uzfh-p-ldst.bin",
420            "rv64uzfh-p-move.bin",
421            "rv64uzfh-p-recoding.bin",
422            "rv64uzvfh-p-vfadd.bin",
423            "rv64uzvfh-p-vfclass.bin",
424            "rv64uzvfh-p-vfcvtfx.bin",
425            "rv64uzvfh-p-vfcvtfxu.bin",
426            "rv64uzvfh-p-vfcvtrxf.bin",
427            "rv64uzvfh-p-vfcvtrxuf.bin",
428            "rv64uzvfh-p-vfcvtxf.bin",
429            "rv64uzvfh-p-vfcvtxuf.bin",
430            "rv64uzvfh-p-vfdiv.bin",
431            "rv64uzvfh-p-vfdown.bin",
432            "rv64uzvfh-p-vfmacc.bin",
433            "rv64uzvfh-p-vfmadd.bin",
434            "rv64uzvfh-p-vfmax.bin",
435            "rv64uzvfh-p-vfmerge.bin",
436            "rv64uzvfh-p-vfmin.bin",
437            "rv64uzvfh-p-vfmsac.bin",
438            "rv64uzvfh-p-vfmsub.bin",
439            "rv64uzvfh-p-vfmul.bin",
440            "rv64uzvfh-p-vfmv.bin",
441            "rv64uzvfh-p-vfncvtff.bin",
442            "rv64uzvfh-p-vfncvtfx.bin",
443            "rv64uzvfh-p-vfncvtfxu.bin",
444            "rv64uzvfh-p-vfncvtrff.bin",
445            "rv64uzvfh-p-vfncvtrxf.bin",
446            "rv64uzvfh-p-vfncvtrxuf.bin",
447            "rv64uzvfh-p-vfncvtxf.bin",
448            "rv64uzvfh-p-vfncvtxuf.bin",
449            "rv64uzvfh-p-vfnmacc.bin",
450            "rv64uzvfh-p-vfnmadd.bin",
451            "rv64uzvfh-p-vfnmsac.bin",
452            "rv64uzvfh-p-vfnmsub.bin",
453            "rv64uzvfh-p-vfrdiv.bin",
454            "rv64uzvfh-p-vfrec7.bin",
455            "rv64uzvfh-p-vfredmax.bin",
456            "rv64uzvfh-p-vfredmin.bin",
457            "rv64uzvfh-p-vfredosum.bin",
458            "rv64uzvfh-p-vfredusum.bin",
459            "rv64uzvfh-p-vfrsqrt7.bin",
460            "rv64uzvfh-p-vfrsub.bin",
461            "rv64uzvfh-p-vfsgnj.bin",
462            "rv64uzvfh-p-vfsgnjn.bin",
463            "rv64uzvfh-p-vfsgnjx.bin",
464            "rv64uzvfh-p-vfsqrt.bin",
465            "rv64uzvfh-p-vfsub.bin",
466            "rv64uzvfh-p-vfup.bin",
467            "rv64uzvfh-p-vfwadd.bin",
468            "rv64uzvfh-p-vfwadd-w.bin",
469            "rv64uzvfh-p-vfwcvtff.bin",
470            "rv64uzvfh-p-vfwcvtfx.bin",
471            "rv64uzvfh-p-vfwcvtfxu.bin",
472            "rv64uzvfh-p-vfwcvtrxf.bin",
473            "rv64uzvfh-p-vfwcvtrxuf.bin",
474            "rv64uzvfh-p-vfwcvtxf.bin",
475            "rv64uzvfh-p-vfwcvtxuf.bin",
476            "rv64uzvfh-p-vfwmacc.bin",
477            "rv64uzvfh-p-vfwmsac.bin",
478            "rv64uzvfh-p-vfwmul.bin",
479            "rv64uzvfh-p-vfwnmacc.bin",
480            "rv64uzvfh-p-vfwnmsac.bin",
481            "rv64uzvfh-p-vfwredosum.bin",
482            "rv64uzvfh-p-vfwredusum.bin",
483            "rv64uzvfh-p-vfwsub.bin",
484            "rv64uzvfh-p-vfwsub-w.bin",
485            "rv64uzvfh-p-vmfeq.bin",
486            "rv64uzvfh-p-vmfge.bin",
487            "rv64uzvfh-p-vmfgt.bin",
488            "rv64uzvfh-p-vmfle.bin",
489            "rv64uzvfh-p-vmflt.bin",
490            "rv64uzvfh-p-vmfne.bin"
491        ]
492        f16_test = map(lambda x: os.path.join(base_dir, x), workloads)
493        return f16_test
494    def __get_ci_zcbtest(self, name=None):
495        base_dir = "/nfs/home/share/ci-workloads/zcb-test"
496        workloads = [
497            "zcb-test-riscv64-xs.bin"
498        ]
499        zcb_test = map(lambda x: os.path.join(base_dir, x), workloads)
500        return zcb_test
501
502    def __get_ci_mc(self, name=None):
503        base_dir = "/nfs/home/share/ci-workloads"
504        workloads = [
505            "nexus-am-workloads/tests/dualcoretest/ldvio-riscv64-xs.bin"
506        ]
507        mc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
508        return mc_tests
509
510    def __get_ci_nodiff(self, name=None):
511        base_dir = "/nfs/home/share/ci-workloads"
512        workloads = [
513            "cache-management/cacheoptest-riscv64-xs.bin"
514        ]
515        tests = map(lambda x: os.path.join(base_dir, x), workloads)
516        return tests
517
518    def __am_apps_path(self, bench):
519        base_dir = '/nfs/home/share/ci-workloads/nexus-am-workloads/apps'
520        filename = f"{bench}-riscv64-xs.bin"
521        return [os.path.join(base_dir, bench, filename)]
522
523    def __get_ci_workloads(self, name):
524        workloads = {
525            "linux-hello": "bbl.bin",
526            "linux-hello-smp": "bbl.bin",
527            "linux-hello-opensbi": "fw_payload.bin",
528            "linux-hello-smp-opensbi": "fw_payload.bin",
529            "linux-hello-new": "bbl.bin",
530            "linux-hello-smp-new": "bbl.bin",
531            "povray": "_700480000000_.gz",
532            "mcf": "_17520000000_.gz",
533            "xalancbmk": "_266100000000_.gz",
534            "gcc": "_39720000000_.gz",
535            "namd": "_434640000000_.gz",
536            "milc": "_103620000000_.gz",
537            "lbm": "_140840000000_.gz",
538            "gromacs": "_275480000000_.gz",
539            "wrf": "_1916220000000_.gz",
540            "astar": "_122060000000_.gz"
541        }
542        if name in workloads:
543            return [os.path.join("/nfs/home/share/ci-workloads", name, workloads[name])]
544        # select a random SPEC checkpoint
545        assert(name == "random")
546        all_cpt_dir = [
547            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_o2_20m/take_cpt",
548            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_o3_20m/take_cpt",
549            "/nfs/home/share/checkpoints_profiles/spec06_rv64gc_o2_20m/take_cpt",
550            "/nfs/home/share/checkpoints_profiles/spec06_rv64gc_o2_50m/take_cpt",
551            "/nfs/home/share/checkpoints_profiles/spec17_rv64gcb_o2_20m/take_cpt",
552            "/nfs/home/share/checkpoints_profiles/spec17_rv64gcb_o3_20m/take_cpt",
553            "/nfs/home/share/checkpoints_profiles/spec17_rv64gc_o2_50m/take_cpt",
554            "/nfs/home/share/checkpoints_profiles/spec17_speed_rv64gcb_o3_20m/take_cpt",
555            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_O3_20m_gcc12.2.0-intFpcOff-jeMalloc/zstd-checkpoint-0-0-0",
556            "/nfs/home/share/checkpoints_profiles/spec06_gcc15_rv64gcbv_O3_lto_base_nemu_single_core_NEMU_archgroup_2024-10-12-16-05/checkpoint-0-0-0"
557        ]
558        all_gcpt = load_all_gcpt(all_cpt_dir)
559        return [random.choice(all_gcpt)]
560
561    def run_ci(self, test):
562        all_tests = {
563            "cputest": self.__get_ci_cputest,
564            "riscv-tests": self.__get_ci_rvtest,
565            "misc-tests": self.__get_ci_misc,
566            "mc-tests": self.__get_ci_mc,
567            "nodiff-tests": self.__get_ci_nodiff,
568            "rvh-tests": self.__get_ci_rvhtest,
569            "microbench": self.__am_apps_path,
570            "coremark": self.__am_apps_path,
571            "coremark-1-iteration": self.__am_apps_path,
572            "rvv-bench": self.__get_ci_rvvbench,
573            "rvv-test": self.__get_ci_rvvtest,
574            "f16_test": self.__get_ci_F16test,
575            "zcb-test": self.__get_ci_zcbtest
576        }
577        for target in all_tests.get(test, self.__get_ci_workloads)(test):
578            print(target)
579            ret = self.run_emu(target)
580            if ret:
581                if self.args.default_wave_home != self.args.wave_home:
582                    print("copy wave file to " + self.args.wave_home)
583                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.vcd $WAVE_HOME")
584                    self.__exec_cmd(f"cp $NOOP_HOME/build/emu $WAVE_HOME")
585                    self.__exec_cmd(f"cp $NOOP_HOME/build/rtl/SimTop.v $WAVE_HOME")
586                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.db $WAVE_HOME")
587                return ret
588        return 0
589
590    def run_ci_vcs(self, test):
591        all_tests = {
592            "cputest": self.__get_ci_cputest,
593            "riscv-tests": self.__get_ci_rvtest,
594            "misc-tests": self.__get_ci_misc,
595            "mc-tests": self.__get_ci_mc,
596            "nodiff-tests": self.__get_ci_nodiff,
597            "rvh-tests": self.__get_ci_rvhtest,
598            "microbench": self.__am_apps_path,
599            "coremark": self.__am_apps_path,
600            "coremark-1-iteration": self.__am_apps_path,
601            "rvv-bench": self.__get_ci_rvvbench,
602            "rvv-test": self.__get_ci_rvvtest,
603            "f16_test": self.__get_ci_F16test,
604            "zcb-test": self.__get_ci_zcbtest
605        }
606        for target in all_tests.get(test, self.__get_ci_workloads)(test):
607            print(target)
608            ret = self.run_simv(target)
609            if ret:
610                if self.args.default_wave_home != self.args.wave_home:
611                    print("copy wave file to " + self.args.wave_home)
612                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.fsdb $WAVE_HOME")
613                    self.__exec_cmd(f"cp $NOOP_HOME/build/simv $WAVE_HOME")
614                    self.__exec_cmd(f"cp $NOOP_HOME/build/rtl/SimTop.v $WAVE_HOME")
615                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.db $WAVE_HOME")
616                return ret
617        return 0
618
619def get_free_cores(n):
620    numa_re = re.compile(r'.*numactl +.*-C +([0-9]+)-([0-9]+).*')
621    while True:
622        disable_cores = []
623        for proc in psutil.process_iter():
624            try:
625                joint = ' '.join(proc.cmdline())
626                numa_match = numa_re.match(joint)
627                if numa_match and 'ssh' not in proc.name():
628                    disable_cores.extend(range(int(numa_match.group(1)), int(numa_match.group(2)) + 1))
629            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
630                pass
631        num_logical_core = psutil.cpu_count(logical=False)
632        core_usage = psutil.cpu_percent(interval=1, percpu=True)
633        num_window = num_logical_core // n
634        for i in range(num_window):
635            if set(disable_cores) & set(range(i * n, i * n + n)):
636                continue
637            window_usage = core_usage[i * n : i * n + n]
638            if sum(window_usage) < 30 * n and True not in map(lambda x: x > 90, window_usage):
639                return (((i * n) % num_logical_core) // (num_logical_core // 2), i * n, i * n + n - 1)
640        print(f"No free {n} cores found. CPU usage: {core_usage}\n")
641        time.sleep(random.uniform(1, 60))
642
643if __name__ == "__main__":
644    parser = argparse.ArgumentParser(description='Python wrapper for XiangShan')
645    parser.add_argument('workload', nargs='?', type=str, default="",
646                        help='input workload file in binary format')
647    # actions
648    parser.add_argument('--build', action='store_true', help='build XS emu')
649    parser.add_argument('--generate', action='store_true', help='generate XS verilog')
650    parser.add_argument('--vcs-gen', action='store_true', help='generate XS sim verilog for vcs')
651    parser.add_argument('--vcs-build', action='store_true', help='build XS simv')
652    parser.add_argument('--ci', nargs='?', type=str, const="", help='run CI tests')
653    parser.add_argument('--ci-vcs', nargs='?', type=str, const="", help='run CI tests on simv')
654    parser.add_argument('--clean', action='store_true', help='clean up XiangShan CI workspace')
655    parser.add_argument('--timeout', nargs='?', type=int, default=None, help='timeout (in seconds)')
656    # environment variables
657    parser.add_argument('--nemu', nargs='?', type=str, help='path to nemu')
658    parser.add_argument('--am', nargs='?', type=str, help='path to nexus-am')
659    parser.add_argument('--dramsim3', nargs='?', type=str, help='path to dramsim3')
660    parser.add_argument('--rvtest', nargs='?', type=str, help='path to riscv-tests')
661    parser.add_argument('--wave-dump', nargs='?', type=str , help='path to dump wave')
662    # chisel arguments
663    parser.add_argument('--enable-log', action='store_true', help='enable log')
664    parser.add_argument('--num-cores', type=int, help='number of cores')
665    # makefile arguments
666    parser.add_argument('--release', action='store_true', help='enable release')
667    parser.add_argument('--spike', action='store_true', help='enable spike diff')
668    parser.add_argument('--with-dramsim3', action='store_true', help='enable dramsim3')
669    parser.add_argument('--threads', nargs='?', type=int, help='number of emu threads')
670    parser.add_argument('--trace', action='store_true', help='enable vcd waveform')
671    parser.add_argument('--trace-fst', action='store_true', help='enable fst waveform')
672    parser.add_argument('--config', nargs='?', type=str, help='config')
673    parser.add_argument('--emu-optimize', nargs='?', type=str, help='verilator optimization letter')
674    parser.add_argument('--xprop', action='store_true', help='enable xprop for vcs')
675    # emu arguments
676    parser.add_argument('--numa', action='store_true', help='use numactl')
677    parser.add_argument('--diff', nargs='?', default="./ready-to-run/riscv64-nemu-interpreter-so", type=str, help='nemu so')
678    parser.add_argument('--max-instr', nargs='?', type=int, help='max instr')
679    parser.add_argument('--disable-fork', action='store_true', help='disable lightSSS')
680    parser.add_argument('--no-diff', action='store_true', help='disable difftest')
681    parser.add_argument('--ram-size', nargs='?', type=str, help='manually set simulation memory size (8GB by default)')
682    parser.add_argument('--gcpt-restore-bin', type=str, default="", help="specify the bin used to restore from gcpt")
683    # both makefile and emu arguments
684    parser.add_argument('--no-db', action='store_true', help='disable chiseldb dump')
685    parser.add_argument('--pgo', nargs='?', type=str, help='workload for pgo (null to disable pgo)')
686    parser.add_argument('--pgo-max-cycle', nargs='?', default=400000, type=int, help='maximun cycle to train pgo')
687    parser.add_argument('--pgo-emu-args', nargs='?', default='--no-diff', type=str, help='emu arguments for pgo')
688    parser.add_argument('--llvm-profdata', nargs='?', type=str, help='corresponding llvm-profdata command of clang to compile emu, do not set with GCC')
689
690    args = parser.parse_args()
691
692    xs = XiangShan(args)
693    ret = xs.run(args)
694
695    sys.exit(ret)
696