nsdk_runner.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. #!/bin/env python3
  2. import os
  3. import sys
  4. SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
  5. requirement_file = os.path.abspath(os.path.join(SCRIPT_DIR, "..", "requirements.txt"))
  6. try:
  7. import time
  8. import random
  9. import json
  10. import argparse
  11. import pprint
  12. import glob
  13. import yaml
  14. import usb
  15. import shutil
  16. import tempfile
  17. import yamale
  18. except:
  19. print("Please install requried packages using: pip3 install -r %s" % (requirement_file))
  20. sys.exit(1)
  21. from nsdk_utils import *
  22. from nsdk_report import *
  23. from nsdk_bench import nsdk_bench
  24. RUNNER_LIST = ["fpga", "ncycm", "qemu", "xlspike"]
  25. GLOBAL_BITSTRAM = None
  26. GLOBAL_FPGASERIAL = None
  27. def global_program_bit():
  28. global GLOBAL_BITSTRAM
  29. global GLOBAL_FPGASERIAL
  30. if GLOBAL_BITSTRAM and GLOBAL_FPGASERIAL:
  31. return program_fpga(GLOBAL_BITSTRAM, GLOBAL_FPGASERIAL)
  32. return False
  33. def set_fpga_bit(bit, serial):
  34. global GLOBAL_BITSTRAM
  35. global GLOBAL_FPGASERIAL
  36. GLOBAL_BITSTRAM = bit
  37. GLOBAL_FPGASERIAL = serial
  38. pass
  39. def yaml_validate(sf, yf):
  40. try:
  41. schema = yamale.make_schema(sf)
  42. data = yamale.make_data(yf)
  43. yamale.validate(schema, data)
  44. return True
  45. except:
  46. return False
  47. def check_usb_serial(serno):
  48. return True
  49. # TODO: pyusb will make usb in virtualbox lost, disable it for now
  50. busses = usb.busses()
  51. for bus in busses:
  52. devices = bus.devices
  53. try:
  54. for dev in devices:
  55. serialnum = usb.util.get_string(dev.dev, dev.iSerialNumber)
  56. if serialnum == serno:
  57. return True
  58. except:
  59. continue
  60. return False
  61. def gen_STATUS(logdir, status):
  62. statusfile = os.path.join(logdir, "STATUS.txt")
  63. with open(statusfile, "w") as sf:
  64. sf.write("%s" % (status))
  65. pass
  66. class nsdk_runner(object):
  67. def __init__(self, sdk, makeopts, runyaml, locations=dict(), verbose=False, timeout=None):
  68. if os.path.isdir(sdk) == False:
  69. print("Invalid sdk path %s" % (sdk))
  70. sys.exit(1)
  71. runner_schema = os.path.join(os.path.dirname(os.path.realpath(__file__)), "runner_schema.yaml")
  72. if yaml_validate(runner_schema, runyaml) == False:
  73. print("Invalid runner yaml file %s" % (runyaml))
  74. sys.exit(1)
  75. yret, self.runcfg = load_yaml(runyaml)
  76. self.sdk = sdk
  77. self.makeopts = makeopts
  78. self.verbose = verbose
  79. self.runtmout = timeout
  80. if yret != YAML_OK:
  81. print("Invalid yaml configuration file %s" % (runyaml))
  82. sys.exit(1)
  83. self.yamlloc = os.path.dirname(runyaml)
  84. if locations.get("fpgaloc", None):
  85. self.runcfg["environment"]["fpgaloc"] = locations["fpgaloc"]
  86. if locations.get("ncycmloc", None):
  87. self.runcfg["environment"]["ncycmloc"] = locations["ncycmloc"]
  88. if locations.get("cfgloc", None):
  89. self.runcfg["environment"]["cfgloc"] = locations["cfgloc"]
  90. self.cpuruncfgs = dict()
  91. for key in self.runcfg["configs"]:
  92. self.cpuruncfgs[key] = copy.deepcopy(self.get_runcfg(key))
  93. pass
  94. def get_runcfg(self, config):
  95. if config not in self.runcfg.get("configs", dict()):
  96. print("ERROR: %s not found in runner yaml config" % (config))
  97. return None
  98. cur_runcfg = self.runcfg["configs"][config]
  99. fpga = cur_runcfg.get("fpga", None)
  100. bitstream = cur_runcfg.get("bitstream", None)
  101. openocdcfg = cur_runcfg.get("openocd_cfg", None)
  102. appcfg = cur_runcfg.get("appcfg", None)
  103. hwcfg = cur_runcfg.get("hwcfg", None)
  104. ncycm = cur_runcfg.get("ncycm", None)
  105. fpgas2run = dict()
  106. for runner in self.runcfg["fpga_runners"]:
  107. if self.runcfg["fpga_runners"][runner]["board_type"] == fpga:
  108. fpgas2run[runner] = self.runcfg["fpga_runners"][runner]
  109. fpgas2run[runner]["bitstream"] = bitstream
  110. fpgas2run[runner]["openocd_cfg"] = openocdcfg
  111. ncycm2run = dict()
  112. for runner in self.runcfg["ncycm_runners"]:
  113. if runner == ncycm:
  114. ncycm2run = self.runcfg["ncycm_runners"][runner]
  115. break
  116. benchcfg = {"appcfg": appcfg, "hwcfg": hwcfg}
  117. return {"benchcfg": benchcfg, "fpga": fpgas2run, "ncycm": ncycm2run}
  118. pass
  119. def get_configs(self):
  120. return list(self.cpuruncfgs.keys())
  121. def run_config(self, config, logdir, runon="", createsubdir=True):
  122. runcfg = self.cpuruncfgs.get(config, None)
  123. if runcfg is None:
  124. return False
  125. if runon not in RUNNER_LIST:
  126. runon = "NOTRUN"
  127. print("No need to run on any target")
  128. benchcfg = runcfg["benchcfg"]
  129. appcfg_file = benchcfg["appcfg"]
  130. hwcfg_file = benchcfg["hwcfg"]
  131. cfgloc = self.runcfg["environment"]["cfgloc"]
  132. def get_file_path(fl, loc):
  133. if os.path.isfile(fl) == False:
  134. nfile = os.path.join(loc, fl)
  135. if os.path.isfile(nfile) == False:
  136. fl = os.path.join(self.yamlloc, fl)
  137. else:
  138. fl = nfile
  139. return fl
  140. appcfg_file = get_file_path(appcfg_file, cfgloc)
  141. hwcfg_file = get_file_path(hwcfg_file, cfgloc)
  142. ret, run_appcfg = load_json(appcfg_file)
  143. if ret != JSON_OK:
  144. print("ERROR: appcfg json file is not a valid configuration file!")
  145. ret, run_hwcfg = load_json(hwcfg_file)
  146. if ret != JSON_OK:
  147. print("ERROR: hwcfg json file is not a valid configuration file!")
  148. final_appcfg = merge_two_config(run_appcfg, run_hwcfg)
  149. if "run_config" not in final_appcfg:
  150. final_appcfg["run_config"] = dict()
  151. need2run = True
  152. # check fpga/ncycm runner
  153. if runon == "fpga":
  154. if len(runcfg["fpga"]) == 0:
  155. print("ERROR: No fpga board available for this cpu")
  156. return False
  157. fpgaready = False
  158. ftdi_serial = serport = ""
  159. for fpga in runcfg["fpga"]:
  160. # check fpga and bitstream, serial port, ftdi_serial
  161. ftdi_serial = runcfg["fpga"][fpga]["ftdi_serial"]
  162. fpga_serial = runcfg["fpga"][fpga]["fpga_serial"]
  163. bitstream = runcfg["fpga"][fpga]["bitstream"]
  164. fpgaloc = self.runcfg["environment"]["fpgaloc"]
  165. serport = runcfg["fpga"][fpga]["serial_port"]
  166. openocdcfg = os.path.join(self.sdk, runcfg["fpga"][fpga]["openocd_cfg"])
  167. if fpga_serial == INVAILD_SERNO or ftdi_serial == INVAILD_SERNO:
  168. print("Invalid fpga or ftdi serial, please check!")
  169. continue
  170. if (os.path.isfile(openocdcfg)) == False:
  171. print("OpenOCD Configuration File %s not found" % (openocdcfg))
  172. continue
  173. bitstream = get_file_path(bitstream, fpgaloc)
  174. if os.path.isfile(bitstream) == False:
  175. print("Bitstream %s not found!" % (bitstream))
  176. continue
  177. if check_usb_serial(ftdi_serial) == False:
  178. print("FDTI Serial %s not found!" % (ftdi_serial))
  179. continue
  180. # program fpga, retry 3 times, and wait for 30s * times for each retry
  181. fpgadone = False
  182. for i in range(3):
  183. if program_fpga(bitstream, fpga_serial) == False:
  184. print("Failed to program fpga using bit %s to target %s, retry times %d" % (bitstream, fpga_serial, i))
  185. slptm = (30 * (i+1)) + random.randint(0, 30)
  186. time.sleep(slptm)
  187. continue
  188. else:
  189. print("Successfully program fpga using bit %s to target %s" % (bitstream, fpga_serial))
  190. fpgadone = True
  191. break
  192. if fpgadone == False:
  193. continue
  194. # after program bit, serial port might change, view in virtual machine
  195. # so just check the serial port after program fpga
  196. if serport is None or serport.strip() == "":
  197. serport = find_serport_by_no(ftdi_serial)
  198. if serport is None:
  199. continue
  200. if check_serial_port(serport) == False:
  201. print("Serial port %s not found!" % (serport))
  202. continue
  203. print("Using Serial Port %s" %(serport))
  204. # only fpga programmed and serial port ready make this fpgaready to true
  205. fpgaready = True
  206. break
  207. # check fpga and serial port is ready or not
  208. if fpgaready == False:
  209. print("FPGA is not ready for running, please check!")
  210. return False
  211. # Modify openocd config
  212. if modify_openocd_cfg(openocdcfg, ftdi_serial) == False:
  213. print("Not a valid openocd configuration file %s" % (openocdcfg))
  214. return False
  215. # set run target to hardware
  216. final_appcfg["run_config"]["target"] = "hardware"
  217. if "hardware" not in final_appcfg["run_config"]:
  218. final_appcfg["run_config"]["hardware"] = {"baudrate": 115200, "timeout": 60, "serport": ""}
  219. final_appcfg["run_config"]["hardware"]["serport"] = serport
  220. final_appcfg["run_config"]["hardware"]["fpgabit"] = bitstream
  221. # set a realpath variable for fpga bitstream for better debug
  222. final_appcfg["run_config"]["hardware"]["fpgabit_realpath"] = os.path.realpath(bitstream)
  223. final_appcfg["run_config"]["hardware"]["fpgaserial"] = fpga_serial
  224. # set bitstream and fpga serial
  225. #set_fpga_bit(bitstream, fpga_serial)
  226. elif runon == "ncycm":
  227. # check ncycm
  228. if len(runcfg["ncycm"]) == 0:
  229. print("ERROR: No cycle model available for this cpu")
  230. return False
  231. ncycm_path = runcfg["ncycm"]["model"]
  232. ncycm_loc = self.runcfg["environment"]["ncycmloc"]
  233. ncycm_path = get_file_path(ncycm_path, ncycm_loc)
  234. if os.path.isfile(ncycm_path) == False:
  235. print("cycle model %s not found!" % (ncycm_path))
  236. return False
  237. # set run target to ncycm
  238. final_appcfg["run_config"]["target"] = "ncycm"
  239. if "ncycm" not in final_appcfg["run_config"]:
  240. final_appcfg["run_config"]["ncycm"] = {"timeout": 1200, "ncycm": "ncycm"}
  241. final_appcfg["run_config"]["ncycm"]["ncycm"] = ncycm_path
  242. final_appcfg["run_config"]["ncycm"]["ncycm_realpath"] = os.path.realpath(ncycm_path)
  243. elif runon == "qemu" or runon == "xlspike":
  244. # set run target to ncycm
  245. final_appcfg["run_config"]["target"] = runon
  246. if runon not in final_appcfg["run_config"]:
  247. final_appcfg["run_config"][runon] = {"timeout": 600}
  248. else:
  249. # don't need to run
  250. need2run = False
  251. # run on fpga/ncycm
  252. nsdk_ext = nsdk_bench()
  253. ret = True
  254. if self.makeopts is None:
  255. mkopts = ""
  256. else:
  257. mkopts = self.makeopts
  258. if self.runtmout:
  259. print("Force %s runner timeout to be %s seconds" % (runon, self.runtmout))
  260. if runon == "fpga":
  261. final_appcfg["run_config"]["hardware"]["timeout"] = int(self.runtmout)
  262. elif runon in ["xlspike", "qemu", "ncycm"]:
  263. final_appcfg["run_config"][runon]["timeout"] = int(self.runtmout)
  264. if runon == "ncycm" or runon == "xlspike":
  265. for opts in ("SIMULATION=1", "SIMU=xlspike"):
  266. if opts not in mkopts:
  267. mkopts = "%s %s" % (mkopts, opts)
  268. elif runon == "qemu":
  269. for opts in ("SIMULATION=1", "SIMU=qemu"):
  270. if opts not in mkopts:
  271. mkopts = "%s %s" % (mkopts, opts)
  272. subappcfg = final_appcfg
  273. if mkopts != "":
  274. subappcfg = merge_config_with_makeopts(subappcfg, mkopts)
  275. if createsubdir:
  276. sublogdir = os.path.join(logdir, config)
  277. else:
  278. sublogdir = logdir
  279. start_time = time.time()
  280. if need2run:
  281. #if runon == "fpga":
  282. # nsdk_ext.set_cpu_hangup_action(global_program_bit)
  283. cmdsts, result = nsdk_ext.run_apps(subappcfg, self.verbose, sublogdir, False)
  284. else:
  285. cmdsts, result = nsdk_ext.build_apps(subappcfg, self.verbose, sublogdir, False)
  286. runtime = round(time.time() - start_time, 2)
  287. print("Build or Run application for config %s run status: %s, time cost %s seconds" % (config, cmdsts, runtime))
  288. locret = check_expected(result, subappcfg, need2run)
  289. print("Application build as expected: %s" % (locret))
  290. # if build or run apps failed, it also should fail unless the locret status
  291. if locret == False or cmdsts == False:
  292. ret = False
  293. # generate STATUS.txt file in log directory
  294. gen_STATUS(sublogdir, ret)
  295. save_results(subappcfg, None, subappcfg, result, sublogdir)
  296. if result:
  297. # Generate build or run report
  298. save_report_files(sublogdir, subappcfg, result, need2run)
  299. return ret
  300. pass
  301. def merge_cfgyaml(appyaml, runyaml, confloc=None, appcfgjf=None, ocdcfg=None):
  302. yret, appcfg = load_yaml(appyaml)
  303. if yret != YAML_OK:
  304. print("Invalid yaml configuration file %s" % (appyaml))
  305. sys.exit(1)
  306. if os.path.isfile(runyaml):
  307. yret, runcfg = load_yaml(runyaml)
  308. if yret != YAML_OK:
  309. print("Invalid yaml configuration file %s" % (runyaml))
  310. sys.exit(1)
  311. else:
  312. runcfg = dict()
  313. if confloc:
  314. appcfg["environment"]["cfgloc"] = confloc
  315. if appcfgjf:
  316. for cfgname in appcfg["configs"]:
  317. appcfg["configs"][cfgname]["appcfg"] = appcfgjf
  318. if ocdcfg:
  319. for cfgname in appcfg["configs"]:
  320. appcfg["configs"][cfgname]["openocd_cfg"] = ocdcfg
  321. for runner in ["ncycm_runners", "fpga_runners"]:
  322. if runner in appcfg and runner in runcfg:
  323. appcfg[runner] = runcfg[runner]
  324. return merge_two_config(appcfg, runcfg)
  325. def prepare_yaml(appyaml, runyaml, logdir, appdirs=None, ocdcfg=None):
  326. if not (appyaml or appdirs):
  327. print("Must provide at least appyaml or appdirs")
  328. return None
  329. usesys = False
  330. confloc = None
  331. appcfgjf = None
  332. conftype = "mini"
  333. if os.path.isdir(logdir) == False:
  334. os.makedirs(logdir)
  335. if appyaml is None or os.path.isfile(appyaml) == False:
  336. if appdirs is None:
  337. print("appyaml or appdirs is invalid, please check!")
  338. return None
  339. sysappyaml = os.path.join(SCRIPT_DIR, "configs", "cpu", "cpu.yaml")
  340. sysappjson = os.path.join(SCRIPT_DIR, "configs", "cpu", "cpu.json")
  341. print("appyaml specified file %s doesn't exit, use system one %s" % (appyaml, sysappyaml))
  342. if appyaml in ["mini", "full"]:
  343. conftype = appyaml
  344. confloc = os.path.join(SCRIPT_DIR, "configs", "cpu", conftype)
  345. appyaml = sysappyaml
  346. ret, appjsoncfg = load_json(sysappjson)
  347. if ret != JSON_OK:
  348. print("System cpu json %s is invalid" % (sysappjson))
  349. return None
  350. appjsoncfg["appdirs"] = appdirs.split(",")
  351. appcfgjf = os.path.join(logdir, "appcases.json")
  352. save_json(appcfgjf, appjsoncfg)
  353. # merge appyaml, runyaml
  354. mergedcfg = merge_cfgyaml(appyaml, runyaml, confloc, appcfgjf, ocdcfg)
  355. runneryaml = os.path.join(args.logdir, "runner.yaml")
  356. save_yaml(runneryaml, mergedcfg)
  357. print("Save used runner yaml %s" % (runneryaml))
  358. return runneryaml
  359. if __name__ == '__main__':
  360. parser = argparse.ArgumentParser(description="Nuclei SDK Benchmark and Report Tool")
  361. parser.add_argument('--appyaml', '--runset', dest='appyaml', help="Application YAML Configuration File, if not specified, will use generated specified by appdirs, contains n300/n600/n900/ux600/ux900")
  362. parser.add_argument('--appdirs', help="App or test cases directories")
  363. parser.add_argument('--runyaml', default="req_runners.yaml", help="Runner YAML Configuration File, default is req_runners.yaml")
  364. parser.add_argument('--logdir', default='logs', help="logs directory, default logs")
  365. parser.add_argument('--fpgaloc', help="Where fpga bitstream located in")
  366. parser.add_argument('--ncycmloc', help="Where nuclei cycle model located in")
  367. parser.add_argument('--cfgloc', help="Where nsdk bench configurations located in")
  368. parser.add_argument('--sdk', help="Where SDK located in")
  369. parser.add_argument('--ocdcfg', help="OpenOCD Configuration File location relative to SDK, such as SoC/evalsoc/Board/nuclei_fpga_eval/openocd_evalsoc.cfg")
  370. parser.add_argument('--make_options', help="Extra make options passed to overwrite default build configuration passed via appcfg and hwcfg")
  371. parser.add_argument('--config', help="Configurations to be run, split via comma, such as n300,n600")
  372. parser.add_argument('--runon', default='qemu', choices=RUNNER_LIST, help="Where to run these application")
  373. parser.add_argument('--show', action='store_true', help="Show configurations")
  374. parser.add_argument('--timeout', help="Runner timeout for each application run, if not specified will use default one specified in json configuration")
  375. parser.add_argument('--verbose', action='store_true', help="If specified, will show detailed build/run messsage")
  376. parser.add_argument('--uniqueid', default="", help="Pass pipeline$CI_PIPELINE_ID, such as pipeline123456")
  377. args = parser.parse_args()
  378. if args.sdk is None:
  379. sdk_path = os.environ.get("NUCLEI_SDK_ROOT")
  380. if sdk_path is None or os.path.isdir(sdk_path) == False:
  381. args.sdk = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../../"))
  382. else:
  383. args.sdk = sdk_path
  384. if not(args.sdk and os.path.isdir(args.sdk) and os.path.isfile(os.path.join(args.sdk, "npk.yml"))):
  385. print("SDK location %s is invalid, please check!" % (args.sdk))
  386. sys.exit(1)
  387. print("Using sdk path in %s" % (args.sdk))
  388. runneryaml = prepare_yaml(args.appyaml, args.runyaml, args.logdir, args.appdirs, args.ocdcfg)
  389. if runneryaml is None:
  390. print("Can't prepare a valid runner yaml file")
  391. sys.exit(1)
  392. pp = pprint.PrettyPrinter(compact=True)
  393. ret = True
  394. locations = {"fpgaloc": args.fpgaloc, "ncycmloc": args.ncycmloc, "cfgloc": args.cfgloc }
  395. nsdk_ext = nsdk_runner(args.sdk, args.make_options, runneryaml, locations, args.verbose, args.timeout)
  396. if args.show:
  397. print("Here are the supported configs:")
  398. pp.pprint(nsdk_ext.get_configs())
  399. else:
  400. start_time = time.time()
  401. if args.config:
  402. sel_configs = list(set([ key.strip() for key in args.config.split(',')]))
  403. print("Run selected configurations: %s" % (sel_configs))
  404. else:
  405. sel_configs = nsdk_ext.get_configs()
  406. print("Run all the configurations as below:")
  407. pp.pprint(sel_configs)
  408. runcnt = 0
  409. for config in sel_configs:
  410. if config in nsdk_ext.get_configs():
  411. runcnt += 1
  412. print("Run for configuration %s now" % (config))
  413. if nsdk_ext.run_config(config, args.logdir, runon=args.runon) == False:
  414. print("Configuration %s failed" % (config))
  415. ret = False
  416. if runcnt > 1:
  417. # generate total results for all the configs
  418. print("Generate all the reports for this run")
  419. need2run = False
  420. if args.runon in RUNNER_LIST:
  421. need2run = True
  422. generate_report_for_logs(args.logdir, need2run, True)
  423. runtime = round(time.time() - start_time, 2)
  424. print("Cost about %s seconds to do this running, passed %s!" % (runtime, ret))
  425. # Exit with ret value
  426. if ret:
  427. sys.exit(0)
  428. else:
  429. sys.exit(1)