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