xref: /XiangShan/scripts/xiangshan.py (revision 7fbc1cb42a2c96ef89a1dfd0f5f885ccada40c26)
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            "Zawrs/Zawrs-zawrs.bin"
364        ]
365        misc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
366        return misc_tests
367
368    def __get_ci_rvhtest(self, name=None):
369        base_dir = "/nfs/home/share/ci-workloads/H-extension-tests"
370        workloads = [
371            "riscv-hyp-tests/rvh_test.bin",
372            "xvisor_wboxtest/checkpoint.gz",
373            "pointer-masking-test/M_HS_test/rvh_test.bin",
374            "pointer-masking-test/U_test/hint_UMode_hupmm2/rvh_test.bin",
375            "pointer-masking-test/U_test/vu_senvcfgpmm2/rvh_test.bin"
376        ]
377        rvh_tests = map(lambda x: os.path.join(base_dir, x), workloads)
378        return rvh_tests
379
380    def __get_ci_rvvbench(self, name=None):
381        base_dir = "/nfs/home/share/ci-workloads"
382        workloads = [
383            "rvv-bench/poly1305.bin",
384            "rvv-bench/mergelines.bin"
385        ]
386        rvvbench = map(lambda x: os.path.join(base_dir, x), workloads)
387        return rvvbench
388
389    def __get_ci_rvvtest(self, name=None):
390        base_dir = "/nfs/home/share/ci-workloads/V-extension-tests"
391        workloads = [
392            "rvv-test/vluxei32.v-0.bin",
393            "rvv-test/vlsseg4e32.v-0.bin",
394            "rvv-test/vlseg4e32.v-0.bin",
395            "rvv-test/vsetvl-0.bin",
396            "rvv-test/vsetivli-0.bin",
397            "rvv-test/vsuxei32.v-0.bin",
398            "rvv-test/vse16.v-0.bin",
399            "rvv-test/vsse16.v-1.bin",
400            "rvv-test/vlse32.v-0.bin",
401            "rvv-test/vsetvli-0.bin",
402            "rvv-test/vle16.v-0.bin",
403            "rvv-test/vle32.v-0.bin",
404            "rvv-test/vfsgnj.vv-0.bin",
405            "rvv-test/vfadd.vf-0.bin",
406            "rvv-test/vfsub.vf-0.bin",
407            "rvv-test/vslide1down.vx-0.bin"
408        ]
409        rvv_test = map(lambda x: os.path.join(base_dir, x), workloads)
410        return rvv_test
411
412    def __get_ci_F16test(self, name=None):
413        base_dir = "/nfs/home/share/ci-workloads/vector/F16-tests/build"
414        workloads = [
415            "rv64uzfhmin-p-fzfhmincvt.bin",
416            "rv64uzfh-p-fadd.bin",
417            "rv64uzfh-p-fclass.bin",
418            "rv64uzfh-p-fcmp.bin",
419            "rv64uzfh-p-fcvt.bin",
420            "rv64uzfh-p-fcvt_w.bin",
421            "rv64uzfh-p-fdiv.bin",
422            "rv64uzfh-p-fmadd.bin",
423            "rv64uzfh-p-fmin.bin",
424            "rv64uzfh-p-ldst.bin",
425            "rv64uzfh-p-move.bin",
426            "rv64uzfh-p-recoding.bin",
427            "rv64uzvfh-p-vfadd.bin",
428            "rv64uzvfh-p-vfclass.bin",
429            "rv64uzvfh-p-vfcvtfx.bin",
430            "rv64uzvfh-p-vfcvtfxu.bin",
431            "rv64uzvfh-p-vfcvtrxf.bin",
432            "rv64uzvfh-p-vfcvtrxuf.bin",
433            "rv64uzvfh-p-vfcvtxf.bin",
434            "rv64uzvfh-p-vfcvtxuf.bin",
435            "rv64uzvfh-p-vfdiv.bin",
436            "rv64uzvfh-p-vfdown.bin",
437            "rv64uzvfh-p-vfmacc.bin",
438            "rv64uzvfh-p-vfmadd.bin",
439            "rv64uzvfh-p-vfmax.bin",
440            "rv64uzvfh-p-vfmerge.bin",
441            "rv64uzvfh-p-vfmin.bin",
442            "rv64uzvfh-p-vfmsac.bin",
443            "rv64uzvfh-p-vfmsub.bin",
444            "rv64uzvfh-p-vfmul.bin",
445            "rv64uzvfh-p-vfmv.bin",
446            "rv64uzvfh-p-vfncvtff.bin",
447            "rv64uzvfh-p-vfncvtfx.bin",
448            "rv64uzvfh-p-vfncvtfxu.bin",
449            "rv64uzvfh-p-vfncvtrff.bin",
450            "rv64uzvfh-p-vfncvtrxf.bin",
451            "rv64uzvfh-p-vfncvtrxuf.bin",
452            "rv64uzvfh-p-vfncvtxf.bin",
453            "rv64uzvfh-p-vfncvtxuf.bin",
454            "rv64uzvfh-p-vfnmacc.bin",
455            "rv64uzvfh-p-vfnmadd.bin",
456            "rv64uzvfh-p-vfnmsac.bin",
457            "rv64uzvfh-p-vfnmsub.bin",
458            "rv64uzvfh-p-vfrdiv.bin",
459            "rv64uzvfh-p-vfrec7.bin",
460            "rv64uzvfh-p-vfredmax.bin",
461            "rv64uzvfh-p-vfredmin.bin",
462            "rv64uzvfh-p-vfredosum.bin",
463            "rv64uzvfh-p-vfredusum.bin",
464            "rv64uzvfh-p-vfrsqrt7.bin",
465            "rv64uzvfh-p-vfrsub.bin",
466            "rv64uzvfh-p-vfsgnj.bin",
467            "rv64uzvfh-p-vfsgnjn.bin",
468            "rv64uzvfh-p-vfsgnjx.bin",
469            "rv64uzvfh-p-vfsqrt.bin",
470            "rv64uzvfh-p-vfsub.bin",
471            "rv64uzvfh-p-vfup.bin",
472            "rv64uzvfh-p-vfwadd.bin",
473            "rv64uzvfh-p-vfwadd-w.bin",
474            "rv64uzvfh-p-vfwcvtff.bin",
475            "rv64uzvfh-p-vfwcvtfx.bin",
476            "rv64uzvfh-p-vfwcvtfxu.bin",
477            "rv64uzvfh-p-vfwcvtrxf.bin",
478            "rv64uzvfh-p-vfwcvtrxuf.bin",
479            "rv64uzvfh-p-vfwcvtxf.bin",
480            "rv64uzvfh-p-vfwcvtxuf.bin",
481            "rv64uzvfh-p-vfwmacc.bin",
482            "rv64uzvfh-p-vfwmsac.bin",
483            "rv64uzvfh-p-vfwmul.bin",
484            "rv64uzvfh-p-vfwnmacc.bin",
485            "rv64uzvfh-p-vfwnmsac.bin",
486            "rv64uzvfh-p-vfwredosum.bin",
487            "rv64uzvfh-p-vfwredusum.bin",
488            "rv64uzvfh-p-vfwsub.bin",
489            "rv64uzvfh-p-vfwsub-w.bin",
490            "rv64uzvfh-p-vmfeq.bin",
491            "rv64uzvfh-p-vmfge.bin",
492            "rv64uzvfh-p-vmfgt.bin",
493            "rv64uzvfh-p-vmfle.bin",
494            "rv64uzvfh-p-vmflt.bin",
495            "rv64uzvfh-p-vmfne.bin"
496        ]
497        f16_test = map(lambda x: os.path.join(base_dir, x), workloads)
498        return f16_test
499    def __get_ci_zcbtest(self, name=None):
500        base_dir = "/nfs/home/share/ci-workloads/zcb-test"
501        workloads = [
502            "zcb-test-riscv64-xs.bin"
503        ]
504        zcb_test = map(lambda x: os.path.join(base_dir, x), workloads)
505        return zcb_test
506
507    def __get_ci_mc(self, name=None):
508        base_dir = "/nfs/home/share/ci-workloads"
509        workloads = [
510            "nexus-am-workloads/tests/dualcoretest/ldvio-riscv64-xs.bin"
511        ]
512        mc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
513        return mc_tests
514
515    def __get_ci_nodiff(self, name=None):
516        base_dir = "/nfs/home/share/ci-workloads"
517        workloads = [
518            "cache-management/cacheoptest-riscv64-xs.bin"
519        ]
520        tests = map(lambda x: os.path.join(base_dir, x), workloads)
521        return tests
522
523    def __am_apps_path(self, bench):
524        base_dir = '/nfs/home/share/ci-workloads/nexus-am-workloads/apps'
525        filename = f"{bench}-riscv64-xs.bin"
526        return [os.path.join(base_dir, bench, filename)]
527
528    def __get_ci_workloads(self, name):
529        workloads = {
530            "linux-hello": "bbl.bin",
531            "linux-hello-smp": "bbl.bin",
532            "linux-hello-opensbi": "fw_payload.bin",
533            "linux-hello-smp-opensbi": "fw_payload.bin",
534            "linux-hello-new": "bbl.bin",
535            "linux-hello-smp-new": "bbl.bin",
536            "povray": "_700480000000_.gz",
537            "mcf": "_17520000000_.gz",
538            "xalancbmk": "_266100000000_.gz",
539            "gcc": "_39720000000_.gz",
540            "namd": "_434640000000_.gz",
541            "milc": "_103620000000_.gz",
542            "lbm": "_140840000000_.gz",
543            "gromacs": "_275480000000_.gz",
544            "wrf": "_1916220000000_.gz",
545            "astar": "_122060000000_.gz",
546            "hmmer-Vector": "_6598_0.250135_.zstd"
547        }
548        if name in workloads:
549            return [os.path.join("/nfs/home/share/ci-workloads", name, workloads[name])]
550        # select a random SPEC checkpoint
551        assert(name == "random")
552        all_cpt_dir = [
553            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_o2_20m/take_cpt",
554            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_o3_20m/take_cpt",
555            "/nfs/home/share/checkpoints_profiles/spec06_rv64gc_o2_20m/take_cpt",
556            "/nfs/home/share/checkpoints_profiles/spec06_rv64gc_o2_50m/take_cpt",
557            "/nfs/home/share/checkpoints_profiles/spec17_rv64gcb_o2_20m/take_cpt",
558            "/nfs/home/share/checkpoints_profiles/spec17_rv64gcb_o3_20m/take_cpt",
559            "/nfs/home/share/checkpoints_profiles/spec17_rv64gc_o2_50m/take_cpt",
560            "/nfs/home/share/checkpoints_profiles/spec17_speed_rv64gcb_o3_20m/take_cpt",
561            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_O3_20m_gcc12.2.0-intFpcOff-jeMalloc/zstd-checkpoint-0-0-0",
562            "/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"
563        ]
564        all_gcpt = load_all_gcpt(all_cpt_dir)
565        return [random.choice(all_gcpt)]
566
567    def run_ci(self, test):
568        all_tests = {
569            "cputest": self.__get_ci_cputest,
570            "riscv-tests": self.__get_ci_rvtest,
571            "misc-tests": self.__get_ci_misc,
572            "mc-tests": self.__get_ci_mc,
573            "nodiff-tests": self.__get_ci_nodiff,
574            "rvh-tests": self.__get_ci_rvhtest,
575            "microbench": self.__am_apps_path,
576            "coremark": self.__am_apps_path,
577            "coremark-1-iteration": self.__am_apps_path,
578            "rvv-bench": self.__get_ci_rvvbench,
579            "rvv-test": self.__get_ci_rvvtest,
580            "f16_test": self.__get_ci_F16test,
581            "zcb-test": self.__get_ci_zcbtest
582        }
583        for target in all_tests.get(test, self.__get_ci_workloads)(test):
584            print(target)
585            ret = self.run_emu(target)
586            if ret:
587                if self.args.default_wave_home != self.args.wave_home:
588                    print("copy wave file to " + self.args.wave_home)
589                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.vcd $WAVE_HOME")
590                    self.__exec_cmd(f"cp $NOOP_HOME/build/emu $WAVE_HOME")
591                    self.__exec_cmd(f"cp $NOOP_HOME/build/rtl/SimTop.v $WAVE_HOME")
592                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.db $WAVE_HOME")
593                return ret
594        return 0
595
596    def run_ci_vcs(self, test):
597        all_tests = {
598            "cputest": self.__get_ci_cputest,
599            "riscv-tests": self.__get_ci_rvtest,
600            "misc-tests": self.__get_ci_misc,
601            "mc-tests": self.__get_ci_mc,
602            "nodiff-tests": self.__get_ci_nodiff,
603            "rvh-tests": self.__get_ci_rvhtest,
604            "microbench": self.__am_apps_path,
605            "coremark": self.__am_apps_path,
606            "coremark-1-iteration": self.__am_apps_path,
607            "rvv-bench": self.__get_ci_rvvbench,
608            "rvv-test": self.__get_ci_rvvtest,
609            "f16_test": self.__get_ci_F16test,
610            "zcb-test": self.__get_ci_zcbtest
611        }
612        for target in all_tests.get(test, self.__get_ci_workloads)(test):
613            print(target)
614            ret = self.run_simv(target)
615            if ret:
616                if self.args.default_wave_home != self.args.wave_home:
617                    print("copy wave file to " + self.args.wave_home)
618                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.fsdb $WAVE_HOME")
619                    self.__exec_cmd(f"cp $NOOP_HOME/build/simv $WAVE_HOME")
620                    self.__exec_cmd(f"cp $NOOP_HOME/build/rtl/SimTop.v $WAVE_HOME")
621                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.db $WAVE_HOME")
622                return ret
623        return 0
624
625def get_free_cores(n):
626    numa_re = re.compile(r'.*numactl +.*-C +([0-9]+)-([0-9]+).*')
627    while True:
628        disable_cores = []
629        for proc in psutil.process_iter():
630            try:
631                joint = ' '.join(proc.cmdline())
632                numa_match = numa_re.match(joint)
633                if numa_match and 'ssh' not in proc.name():
634                    disable_cores.extend(range(int(numa_match.group(1)), int(numa_match.group(2)) + 1))
635            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
636                pass
637        num_logical_core = psutil.cpu_count(logical=False)
638        core_usage = psutil.cpu_percent(interval=1, percpu=True)
639        num_window = num_logical_core // n
640        for i in range(num_window):
641            if set(disable_cores) & set(range(i * n, i * n + n)):
642                continue
643            window_usage = core_usage[i * n : i * n + n]
644            if sum(window_usage) < 30 * n and True not in map(lambda x: x > 90, window_usage):
645                return (((i * n) % num_logical_core) // (num_logical_core // 2), i * n, i * n + n - 1)
646        print(f"No free {n} cores found. CPU usage: {core_usage}\n")
647        time.sleep(random.uniform(1, 60))
648
649if __name__ == "__main__":
650    parser = argparse.ArgumentParser(description='Python wrapper for XiangShan')
651    parser.add_argument('workload', nargs='?', type=str, default="",
652                        help='input workload file in binary format')
653    # actions
654    parser.add_argument('--build', action='store_true', help='build XS emu')
655    parser.add_argument('--generate', action='store_true', help='generate XS verilog')
656    parser.add_argument('--vcs-gen', action='store_true', help='generate XS sim verilog for vcs')
657    parser.add_argument('--vcs-build', action='store_true', help='build XS simv')
658    parser.add_argument('--ci', nargs='?', type=str, const="", help='run CI tests')
659    parser.add_argument('--ci-vcs', nargs='?', type=str, const="", help='run CI tests on simv')
660    parser.add_argument('--clean', action='store_true', help='clean up XiangShan CI workspace')
661    parser.add_argument('--timeout', nargs='?', type=int, default=None, help='timeout (in seconds)')
662    # environment variables
663    parser.add_argument('--nemu', nargs='?', type=str, help='path to nemu')
664    parser.add_argument('--am', nargs='?', type=str, help='path to nexus-am')
665    parser.add_argument('--dramsim3', nargs='?', type=str, help='path to dramsim3')
666    parser.add_argument('--rvtest', nargs='?', type=str, help='path to riscv-tests')
667    parser.add_argument('--wave-dump', nargs='?', type=str , help='path to dump wave')
668    # chisel arguments
669    parser.add_argument('--enable-log', action='store_true', help='enable log')
670    parser.add_argument('--num-cores', type=int, help='number of cores')
671    # makefile arguments
672    parser.add_argument('--release', action='store_true', help='enable release')
673    parser.add_argument('--spike', action='store_true', help='enable spike diff')
674    parser.add_argument('--with-dramsim3', action='store_true', help='enable dramsim3')
675    parser.add_argument('--threads', nargs='?', type=int, help='number of emu threads')
676    parser.add_argument('--trace', action='store_true', help='enable vcd waveform')
677    parser.add_argument('--trace-fst', action='store_true', help='enable fst waveform')
678    parser.add_argument('--config', nargs='?', type=str, help='config')
679    parser.add_argument('--yaml-config', nargs='?', type=str, help='yaml config')
680    parser.add_argument('--emu-optimize', nargs='?', type=str, help='verilator optimization letter')
681    parser.add_argument('--xprop', action='store_true', help='enable xprop for vcs')
682    parser.add_argument('--issue', nargs='?', type=str, help='CHI issue')
683    # emu arguments
684    parser.add_argument('--numa', action='store_true', help='use numactl')
685    parser.add_argument('--diff', nargs='?', default="./ready-to-run/riscv64-nemu-interpreter-so", type=str, help='nemu so')
686    parser.add_argument('--max-instr', nargs='?', type=int, help='max instr')
687    parser.add_argument('--disable-fork', action='store_true', help='disable lightSSS')
688    parser.add_argument('--no-diff', action='store_true', help='disable difftest')
689    parser.add_argument('--ram-size', nargs='?', type=str, help='manually set simulation memory size (8GB by default)')
690    parser.add_argument('--gcpt-restore-bin', type=str, default="", help="specify the bin used to restore from gcpt")
691    # both makefile and emu arguments
692    parser.add_argument('--no-db', action='store_true', help='disable chiseldb dump')
693    parser.add_argument('--pgo', nargs='?', type=str, help='workload for pgo (null to disable pgo)')
694    parser.add_argument('--pgo-max-cycle', nargs='?', default=400000, type=int, help='maximun cycle to train pgo')
695    parser.add_argument('--pgo-emu-args', nargs='?', default='--no-diff', type=str, help='emu arguments for pgo')
696    parser.add_argument('--llvm-profdata', nargs='?', type=str, help='corresponding llvm-profdata command of clang to compile emu, do not set with GCC')
697
698    args = parser.parse_args()
699
700    xs = XiangShan(args)
701    ret = xs.run(args)
702
703    sys.exit(ret)
704