nsdk_utils.py 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183
  1. #!/usr/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 shutil
  10. import signal
  11. import psutil
  12. import re
  13. import copy
  14. import serial
  15. import serial.tools.list_ports
  16. import tempfile
  17. import collections
  18. from threading import Thread
  19. import subprocess
  20. import asyncio
  21. import glob
  22. import json
  23. import yaml
  24. import importlib.util
  25. except Exception as exc:
  26. print("Import Error: %s" % (exc))
  27. print("Please install requried packages using: pip3 install -r %s" % (requirement_file))
  28. sys.exit(1)
  29. try:
  30. from collections.abc import Mapping
  31. except ImportError: # Python 2.7 compatibility
  32. from collections import Mapping
  33. SDK_GLOBAL_VARIABLES = {
  34. "sdk_checktag": "Nuclei SDK Build Time:",
  35. "sdk_check": True,
  36. "sdk_banner_tmout": 15,
  37. "sdk_copy_objects": "elf,map",
  38. "sdk_copy_objects_flag": False,
  39. "sdk_ttyerr_maxcnt": 3,
  40. "sdk_fpgaprog_maxcnt": 3,
  41. "sdk_gdberr_maxcnt": 10,
  42. "sdk_uploaderr_maxcnt": 10,
  43. "sdk_bannertmout_maxcnt": 100,
  44. "sdk_verb_buildmsg": True,
  45. "sdk_copy_failobj": True
  46. }
  47. INVAILD_SERNO = "xxxxx"
  48. BANNER_TMOUT = "banner_timeout"
  49. TTY_OP_ERR = "tty_operate_error"
  50. TTY_UNKNOWN_ERR = "tty_unknown_error"
  51. # get ci url information
  52. def get_ci_info():
  53. cijoburl = os.environ.get("CI_JOB_URL")
  54. cipipelineurl = os.environ.get("CI_PIPELINE_URL")
  55. if cijoburl and cipipelineurl:
  56. return {"joburl": cijoburl, "pipelineurl": cipipelineurl}
  57. else:
  58. return {}
  59. def get_global_variables():
  60. return SDK_GLOBAL_VARIABLES
  61. def get_sdk_checktag():
  62. checktag = os.environ.get("SDK_CHECKTAG")
  63. if checktag is None:
  64. checktag = SDK_GLOBAL_VARIABLES.get("sdk_checktag")
  65. return checktag
  66. def get_sdk_copyobjects():
  67. cpobjs = os.environ.get("SDK_COPY_OBJECTS")
  68. if cpobjs is None:
  69. cpobjs = SDK_GLOBAL_VARIABLES.get("sdk_copy_objects")
  70. return cpobjs
  71. def get_env_flag(envar, deft=None):
  72. flag = os.environ.get(envar)
  73. if flag is None:
  74. return deft
  75. return flag.lower() in ('true', '1', 't')
  76. def get_sdk_check():
  77. check = get_env_flag("SDK_CHECK")
  78. if check is None:
  79. check = SDK_GLOBAL_VARIABLES.get("sdk_check")
  80. return check
  81. def get_sdk_verb_buildmsg():
  82. check = get_env_flag("SDK_VERB_BUILDMSG")
  83. if check is None:
  84. check = SDK_GLOBAL_VARIABLES.get("sdk_verb_buildmsg")
  85. return check
  86. def get_sdk_copyobjects_flag():
  87. cpflag = get_env_flag("SDK_COPY_OBJECTS_FLAG")
  88. if cpflag is None:
  89. cpflag = SDK_GLOBAL_VARIABLES.get("sdk_copy_objects_flag")
  90. return cpflag
  91. def get_sdk_need_copyobjects(appconfig):
  92. try:
  93. needed = appconfig.get("copy_objects")
  94. except:
  95. needed = False
  96. if needed != True:
  97. # use global flag
  98. needed = get_sdk_copyobjects_flag()
  99. return needed
  100. def get_sdk_copy_failobj():
  101. cpflag = get_env_flag("SDK_COPY_FAILOBJ")
  102. if cpflag is None:
  103. cpflag = SDK_GLOBAL_VARIABLES.get("sdk_copy_failobj")
  104. return cpflag
  105. def get_sdk_banner_tmout():
  106. tmout = os.environ.get("SDK_BANNER_TMOUT")
  107. if tmout is not None:
  108. tmout = int(tmout)
  109. else:
  110. tmout = SDK_GLOBAL_VARIABLES.get("sdk_banner_tmout")
  111. return tmout
  112. def get_sdk_fpga_prog_tmout():
  113. tmout = os.environ.get("FPGA_PROG_TMOUT")
  114. return tmout
  115. def get_sdk_ttyerr_maxcnt():
  116. num = os.environ.get("SDK_TTYERR_MAXCNT")
  117. if num is not None:
  118. num = int(num)
  119. else:
  120. num = SDK_GLOBAL_VARIABLES.get("sdk_ttyerr_maxcnt")
  121. return num
  122. def get_sdk_fpgaprog_maxcnt():
  123. num = os.environ.get("SDK_FPGAPROG_MAXCNT")
  124. if num is not None:
  125. num = int(num)
  126. else:
  127. num = SDK_GLOBAL_VARIABLES.get("sdk_fpgaprog_maxcnt")
  128. return num
  129. def get_sdk_gdberr_maxcnt():
  130. num = os.environ.get("SDK_GDBERR_MAXCNT")
  131. if num is not None:
  132. num = int(num)
  133. else:
  134. num = SDK_GLOBAL_VARIABLES.get("sdk_gdberr_maxcnt")
  135. return num
  136. def get_sdk_bannertmout_maxcnt():
  137. num = os.environ.get("SDK_BANNERTMOUT_MAXCNT")
  138. if num is not None:
  139. num = int(num)
  140. else:
  141. num = SDK_GLOBAL_VARIABLES.get("sdk_bannertmout_maxcnt")
  142. return num
  143. def get_sdk_uploaderr_maxcnt():
  144. num = os.environ.get("SDK_UPLOADERR_MAXCNT")
  145. if num is not None:
  146. num = int(num)
  147. else:
  148. num = SDK_GLOBAL_VARIABLES.get("sdk_uploaderr_maxcnt")
  149. return num
  150. class NThread(Thread):
  151. def __init__(self, func, args):
  152. super(NThread, self).__init__()
  153. self.func = func
  154. self.args = args
  155. def run(self):
  156. self.result = self.func(*self.args)
  157. def get_result(self):
  158. try:
  159. return self.result
  160. except Exception:
  161. return None
  162. YAML_OK=0
  163. YAML_NOFILE=1
  164. YAML_INVAILD=2
  165. def load_yaml(file):
  166. if isinstance(file, str) == False or os.path.isfile(file) == False:
  167. return YAML_NOFILE, None
  168. try:
  169. data = yaml.load(open(file, 'r'), Loader=yaml.FullLoader)
  170. return YAML_OK, data
  171. except:
  172. print("Error: %s is an invalid yaml file!" % (file))
  173. return YAML_INVAILD, None
  174. def save_yaml(file, data):
  175. if isinstance(file, str) == False:
  176. return False
  177. try:
  178. with open(file, "w") as cf:
  179. yaml.dump(data, cf, indent=4)
  180. return True
  181. except:
  182. print("Error: Data can't be serialized to yaml file!")
  183. return False
  184. def get_specific_key_value(dictdata:dict, key):
  185. if not dictdata:
  186. print("Error: dictdata doesn't exist!")
  187. return None
  188. value = dictdata.get(key, None)
  189. if not value:
  190. print("Error, key %s has no value!" % (key))
  191. return None
  192. return value
  193. JSON_OK=0
  194. JSON_NOFILE=1
  195. JSON_INVAILD=2
  196. def load_json(file):
  197. if isinstance(file, str) == False or os.path.isfile(file) == False:
  198. return JSON_NOFILE, None
  199. try:
  200. data = json.load(open(file, 'r'))
  201. return JSON_OK, data
  202. except:
  203. print("Error: %s is an invalid json file!" % (file))
  204. return JSON_INVAILD, None
  205. def save_json(file, data):
  206. if isinstance(file, str) == False:
  207. return False
  208. try:
  209. with open(file, "w") as cf:
  210. json.dump(data, cf, indent=4)
  211. return True
  212. except:
  213. print("Error: Data can't be serialized to json file!")
  214. return False
  215. def save_csv(file, csvlines, display=True):
  216. if isinstance(csvlines, list) == False:
  217. return False
  218. # Flush stdout buffer
  219. sys.stdout.flush()
  220. try:
  221. with open(file, "w") as cf:
  222. for line in csvlines:
  223. csvline = line + "\n"
  224. cf.write(csvline)
  225. cf.flush()
  226. if display:
  227. try:
  228. # sometimes facing issue BlockingIOError: [Errno 11] write could not complete without blocking here
  229. # maybe related to https://bugs.python.org/issue40634 since we are using async in this tool
  230. sys.stdout.flush()
  231. print("CSV, %s" % line)
  232. except:
  233. pass
  234. return True
  235. except:
  236. print("Error: Data can't be saved to file!")
  237. return False
  238. # Return possible serports, return a list of possible serports
  239. def find_possible_serports():
  240. comports = serial.tools.list_ports.comports()
  241. serports = [ port.device for port in comports ]
  242. return serports
  243. def find_serport_by_no(serno):
  244. comports = serial.tools.list_ports.comports()
  245. serport = None
  246. for port in comports:
  247. cur_serno = port.serial_number
  248. cur_dev = port.device
  249. cur_loc = port.location
  250. if cur_serno is None:
  251. continue
  252. if sys.platform == "win32":
  253. if (serno + 'B') == cur_serno:
  254. serport = cur_dev
  255. break
  256. else:
  257. if serno != cur_serno:
  258. continue
  259. # serial is the second device of the composite device
  260. if cur_loc.endswith(".1"):
  261. serport = cur_dev
  262. break
  263. # serport founded
  264. return serport
  265. def find_most_possible_serport():
  266. serports = find_possible_serports()
  267. if len(serports) > 0:
  268. # sort the ports
  269. serports.sort()
  270. # get the biggest port
  271. # for /dev/ttyUSB0, /dev/ttyUSB1, get /dev/ttyUSB1
  272. # for COM16, COM17, get COM17
  273. return serports[-1]
  274. else:
  275. return None
  276. # get from https://gist.github.com/angstwad/bf22d1822c38a92ec0a9
  277. def dict_merge(dct, merge_dct):
  278. """ Recursive dict merge. Inspired by :meth:``dict.update()``, instead of
  279. updating only top-level keys, dict_merge recurses down into dicts nested
  280. to an arbitrary depth, updating keys. The ``merge_dct`` is merged into
  281. ``dct``.
  282. :param dct: dict onto which the merge is executed
  283. :param merge_dct: dct merged into dct
  284. :return: None
  285. """
  286. for k, v in merge_dct.items():
  287. if (k in dct and isinstance(dct[k], dict)
  288. and isinstance(merge_dct[k], Mapping)):
  289. dict_merge(dct[k], merge_dct[k])
  290. else:
  291. dct[k] = merge_dct[k]
  292. def get_make_csv(app, config):
  293. make_options = " "
  294. SUPPORT_KEYS = ["SOC", "BOARD", "CORE", "DOWNLOAD", "VARIANT", \
  295. "BENCH_UNIT", "BENCH_FLAGS", "ARCH_EXT", "STDCLIB", "SILENT", "V"]
  296. csv_print = "CSV, APP=%s" % (app)
  297. if isinstance(config, dict):
  298. for key in config:
  299. if key not in SUPPORT_KEYS:
  300. continue
  301. option = "%s=%s"%(key, config[key])
  302. make_options = " %s %s " % (make_options, option)
  303. csv_print = "%s, %s" % (csv_print, option)
  304. return make_options, csv_print
  305. def try_decode_bytes(bytes):
  306. ENCODING_LIST = ['utf-8', 'gbk', 'gb18030']
  307. destr = ""
  308. for encoding in ENCODING_LIST:
  309. try:
  310. destr = bytes.decode(encoding)
  311. break
  312. except:
  313. continue
  314. return destr
  315. def kill_async_subprocess(proc):
  316. startticks = time.time()
  317. if proc is not None:
  318. try:
  319. kill_sig = signal.SIGTERM
  320. if sys.platform != "win32":
  321. kill_sig = signal.SIGKILL
  322. print("Try to Kill process id %d now" %(proc.pid))
  323. parent_proc = psutil.Process(proc.pid)
  324. try:
  325. # This might cause PermissionError: [Errno 1] Operation not permitted: '/proc/1/stat' issue
  326. child_procs = parent_proc.children(recursive=True)
  327. for child_proc in child_procs:
  328. print("Kill child process %s, pid %d" %(child_proc.name(), child_proc.pid))
  329. try:
  330. os.kill(child_proc.pid, kill_sig) # kill child process
  331. except:
  332. continue
  333. except Exception as exc:
  334. print("Warning: kill child process failed with %s" %(exc))
  335. if parent_proc.is_running():
  336. print("Kill parent process %s, pid %d" %(parent_proc.name(), parent_proc.pid))
  337. if sys.platform != "win32":
  338. try:
  339. os.killpg(parent_proc.pid, kill_sig) # kill parent process
  340. except:
  341. os.kill(parent_proc.pid, kill_sig) # kill parent process
  342. else:
  343. os.kill(parent_proc.pid, kill_sig) # kill parent process
  344. # kill using process.kill again
  345. if parent_proc.is_running():
  346. proc.kill()
  347. except psutil.NoSuchProcess:
  348. pass
  349. except Exception as exc:
  350. print("Warning: kill process failed with %s" %(exc))
  351. # show time cost for kill process
  352. print("kill process used %.2f seconds" %((time.time() - startticks)))
  353. sys.stdout.flush()
  354. pass
  355. def kill_subprocess(proc):
  356. try:
  357. if proc.poll() is None: # process is still running
  358. kill_async_subprocess(proc)
  359. except:
  360. pass
  361. pass
  362. def import_module(module_name, file_path):
  363. if file_path is None or os.path.isfile(file_path) == False:
  364. return None
  365. try:
  366. spec = importlib.util.spec_from_file_location(module_name, file_path)
  367. module = importlib.util.module_from_spec(spec)
  368. spec.loader.exec_module(module)
  369. except:
  370. module = None
  371. return module
  372. def import_function(func_name, file_path):
  373. module_name = "tempmodule_%s" % (random.randint(0, 10000))
  374. tmpmodule = import_module(module_name, file_path)
  375. if tmpmodule is None:
  376. return None
  377. if func_name not in dir(tmpmodule):
  378. return None
  379. return getattr(tmpmodule, func_name)
  380. COMMAND_RUNOK=0
  381. COMMAND_INVALID=1
  382. COMMAND_FAIL=2
  383. COMMAND_INTERRUPTED=3
  384. COMMAND_EXCEPTION=4
  385. COMMAND_NOTAPP=5
  386. COMMAND_TIMEOUT=6
  387. COMMAND_TIMEOUT_READ=7
  388. RUNSTATUS_OK=0
  389. RUNSTATUS_FAIL=1
  390. RUNSTATUS_NOTSTART=2
  391. def run_command(command, show_output=True, logfile=None, append=False):
  392. logfh = None
  393. ret = COMMAND_RUNOK
  394. cmd_elapsed_ticks = 0
  395. if isinstance(command, str) == False:
  396. return COMMAND_INVALID, cmd_elapsed_ticks
  397. startticks = time.time()
  398. process = None
  399. try:
  400. if isinstance(logfile, str):
  401. if append:
  402. logfh = open(logfile, "ab")
  403. else:
  404. logfh = open(logfile, "wb")
  405. if logfh:
  406. # record command run in log file
  407. logfh.write(("Execute Command %s\n" % (command)).encode())
  408. process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, \
  409. stderr=subprocess.STDOUT)
  410. while True:
  411. line = process.stdout.readline()
  412. if (not line) and process.poll() is not None:
  413. break
  414. if show_output:
  415. print(try_decode_bytes(line), end="")
  416. if logfh:
  417. logfh.write(line)
  418. time.sleep(0.01)
  419. process.communicate(30)
  420. if process.returncode != 0:
  421. ret = COMMAND_FAIL
  422. except (KeyboardInterrupt):
  423. print("Key CTRL-C pressed, command executing stopped!")
  424. ret = COMMAND_INTERRUPTED
  425. except subprocess.TimeoutExpired:
  426. ret = COMMAND_TIMEOUT
  427. except Exception as exc:
  428. print("Unexpected exception happened: %s" %(str(exc)))
  429. ret = COMMAND_EXCEPTION
  430. finally:
  431. kill_subprocess(process)
  432. if process:
  433. del process
  434. if logfh:
  435. logfh.close()
  436. cmd_elapsed_ticks = time.time() - startticks
  437. return ret, cmd_elapsed_ticks
  438. async def run_cmd_and_check_async(command, timeout:int, checks:dict, checktime=time.time(), sdk_check=False, logfile=None, show_output=False, banner_timeout=3):
  439. logfh = None
  440. ret = COMMAND_FAIL
  441. cmd_elapsed_ticks = 0
  442. if isinstance(command, str) == False:
  443. return COMMAND_INVALID, cmd_elapsed_ticks
  444. startticks = time.time()
  445. process = None
  446. check_status = False
  447. pass_checks = checks.get("PASS", [])
  448. fail_checks = checks.get("FAIL", [])
  449. def test_in_check(string, checks):
  450. if type(checks) == list:
  451. for check in checks:
  452. if check in string:
  453. return True
  454. return False
  455. NSDK_CHECK_TAG = get_sdk_checktag()
  456. if get_sdk_verb_buildmsg():
  457. print("Checker used: ", checks)
  458. print("SDK Checker Tag \"%s\", checker enable %s" % (NSDK_CHECK_TAG, sdk_check))
  459. print("SDK run timeout %s, banner timeout %s" % (timeout, banner_timeout))
  460. check_finished = False
  461. start_time = time.time()
  462. serial_log = ""
  463. nsdk_check_timeout = banner_timeout
  464. sdk_checkstarttime = time.time()
  465. try:
  466. if isinstance(logfile, str):
  467. logfh = open(logfile, "wb")
  468. if sys.platform != "win32":
  469. # add exec to running command to avoid create a process called /bin/sh -c
  470. # and if you kill that process it will kill this sh process not the really
  471. # command process you want to kill
  472. process = await asyncio.create_subprocess_shell("exec " + command, \
  473. stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
  474. else:
  475. process = await asyncio.create_subprocess_shell(command, \
  476. stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
  477. while (time.time() - start_time) < timeout:
  478. try:
  479. linebytes = await asyncio.wait_for(process.stdout.readline(), 1)
  480. except asyncio.TimeoutError:
  481. if sdk_check == True:
  482. linebytes = None
  483. else:
  484. continue
  485. except KeyboardInterrupt:
  486. print("Key CTRL-C pressed, command executing stopped!")
  487. break
  488. except:
  489. break
  490. if linebytes:
  491. line = str(try_decode_bytes(linebytes)).replace('\r', '')
  492. else:
  493. line = ""
  494. if sdk_check == True:
  495. if (time.time() - sdk_checkstarttime) > nsdk_check_timeout:
  496. print("No SDK banner found in %s s, quit now!" % (nsdk_check_timeout))
  497. ret = COMMAND_TIMEOUT
  498. check_status = False
  499. break
  500. if line == "":
  501. continue
  502. if show_output:
  503. print("XXX Check " + line, end='')
  504. if NSDK_CHECK_TAG in line:
  505. timestr = line.split(NSDK_CHECK_TAG)[-1].strip()
  506. cur_time = time.mktime(time.strptime(timestr, "%b %d %Y, %H:%M:%S"))
  507. if int(cur_time) >= int(checktime):
  508. sdk_check = False
  509. line = NSDK_CHECK_TAG + " " + timestr + "\n"
  510. serial_log = serial_log + str(line)
  511. else:
  512. serial_log = serial_log + str(line)
  513. if show_output:
  514. print(line, end='')
  515. if check_finished == False:
  516. if test_in_check(line, fail_checks):
  517. check_status = False
  518. check_finished = True
  519. if test_in_check(line, pass_checks):
  520. check_status = True
  521. check_finished = True
  522. if check_finished:
  523. ret = COMMAND_RUNOK
  524. # record another 2 seconds by reset start_time and timeout to 2
  525. start_time = time.time()
  526. timeout = 1
  527. if logfh and linebytes:
  528. logfh.write(linebytes)
  529. time.sleep(0.01)
  530. except (KeyboardInterrupt):
  531. print("Key CTRL-C pressed, command executing stopped!")
  532. ret = COMMAND_INTERRUPTED
  533. except Exception as exc:
  534. print("Unexpected exception happened: %s" %(str(exc)))
  535. ret = COMMAND_EXCEPTION
  536. finally:
  537. # kill this process
  538. kill_async_subprocess(process)
  539. if logfh:
  540. logfh.close()
  541. cmd_elapsed_ticks = time.time() - startticks
  542. return check_status, cmd_elapsed_ticks
  543. def run_cmd_and_check(command, timeout:int, checks:dict, checktime=time.time(), sdk_check=False, logfile=None, show_output=False, banner_timeout=30):
  544. loop = asyncio.get_event_loop()
  545. try:
  546. ret, cmd_elapsed_ticks = loop.run_until_complete( \
  547. run_cmd_and_check_async(command, timeout, checks, checktime, sdk_check, logfile, show_output, banner_timeout))
  548. except KeyboardInterrupt:
  549. print("Key CTRL-C pressed, command executing stopped!")
  550. ret, cmd_elapsed_ticks = False, 0
  551. finally:
  552. if sys.platform != "win32":
  553. os.system("stty echo 2> /dev/null")
  554. return ret, cmd_elapsed_ticks
  555. def find_files(fndir, pattern, recursive=False):
  556. fndir = os.path.normpath(fndir)
  557. files = glob.glob(os.path.join(fndir, pattern), recursive=recursive)
  558. return files
  559. def get_logfile(appdir, startdir, logdir, logname):
  560. relpath = os.path.relpath(appdir, startdir)
  561. _, startdir_basename = os.path.splitdrive(startdir)
  562. applogdir = os.path.join(os.path.relpath(logdir + os.sep + startdir_basename), relpath)
  563. applog = os.path.relpath(os.path.join(applogdir, logname))
  564. applogdir = os.path.dirname(applog)
  565. if os.path.isdir(applogdir) == False:
  566. os.makedirs(applogdir)
  567. return applog
  568. def strtofloat(value):
  569. fval = 0.0
  570. try:
  571. match = re.search(r'[+-]?\d*\.?\d+([Ee][+-]?\d+)?', value.strip())
  572. if match:
  573. fval = float(match.group())
  574. except:
  575. pass
  576. return fval
  577. def check_tool_version(ver_cmd, ver_check):
  578. vercmd_log = tempfile.mktemp()
  579. ret, _ = run_command(ver_cmd, show_output=False, logfile=vercmd_log)
  580. check_sts = False
  581. verstr = None
  582. if ret == COMMAND_RUNOK:
  583. with open(vercmd_log, 'r', errors='ignore') as vlf:
  584. for line in vlf.readlines():
  585. if ver_check in line:
  586. verstr = line.strip()
  587. check_sts = True
  588. break
  589. os.remove(vercmd_log)
  590. return check_sts, verstr
  591. def get_elfsize(elf):
  592. sizeinfo = {"text": -1, "data": -1, "bss": -1, "total": -1}
  593. if os.path.isfile(elf) == False:
  594. return sizeinfo
  595. for sizetool in [ "riscv-nuclei-elf-size", "riscv64-unknown-elf-size", "size" ]:
  596. sizecmd = "%s %s" % (sizetool, elf)
  597. sizelog = tempfile.mktemp()
  598. ret, _ = run_command(sizecmd, show_output=False, logfile=sizelog)
  599. if ret == COMMAND_RUNOK:
  600. with open(sizelog, "r", errors='ignore') as sf:
  601. lines = sf.readlines()
  602. datas = lines[-1].strip().split()
  603. sizeinfo["text"] = int(datas[0])
  604. sizeinfo["data"] = int(datas[1])
  605. sizeinfo["bss"] = int(datas[2])
  606. sizeinfo["total"] = int(datas[3])
  607. os.remove(sizelog)
  608. break
  609. else:
  610. os.remove(sizelog)
  611. return sizeinfo
  612. def merge_config_with_makeopts(config, make_options):
  613. opt_splits=make_options.strip().split()
  614. passed_buildcfg = dict()
  615. for opt in opt_splits:
  616. if "=" in opt:
  617. values = opt.split("=")
  618. # Make new build config
  619. if (len(values) == 2):
  620. passed_buildcfg[values[0]] = values[1]
  621. build_cfg = config.get("build_config", None)
  622. if build_cfg is None:
  623. config["build_config"] = passed_buildcfg
  624. else:
  625. # update build_config using parsed config via values specified in make_options
  626. config["build_config"].update(passed_buildcfg)
  627. return config
  628. # merge config dict and args dict
  629. # args will overwrite config
  630. def merge_config_with_args(config, args_dict):
  631. if isinstance(config, dict) == False:
  632. return None
  633. if isinstance(args_dict, dict) == False:
  634. return config
  635. serport = args_dict.get("serport", None)
  636. baudrate = args_dict.get("baudrate", None)
  637. make_options = args_dict.get("make_options", None)
  638. parallel = args_dict.get("parallel", None)
  639. build_target = args_dict.get("build_target", None)
  640. run_target = args_dict.get("run_target", None)
  641. timeout = args_dict.get("timeout", None)
  642. ncycm = args_dict.get("ncycm", None)
  643. if isinstance(config, dict) == False:
  644. return None
  645. new_config = copy.deepcopy(config)
  646. if serport or baudrate or run_target:
  647. run_cfg = new_config.get("run_config", None)
  648. if run_cfg is None:
  649. new_config["run_config"] = {"hardware":{}}
  650. elif "hardware" not in run_cfg:
  651. new_config["run_config"]["hardware"] = {}
  652. if serport:
  653. new_config["run_config"]["hardware"]["serport"] = str(serport)
  654. if baudrate:
  655. new_config["run_config"]["hardware"]["serport"] = int(baudrate)
  656. if run_target:
  657. new_config["run_config"]["target"] = str(run_target)
  658. run_target = new_config["run_config"].get("target", "hardware")
  659. if run_target not in new_config["run_config"]:
  660. new_config["run_config"][run_target] = dict()
  661. if ncycm:
  662. if "ncycm" not in new_config["run_config"]:
  663. new_config["run_config"]["ncycm"] = dict()
  664. new_config["run_config"]["ncycm"]["ncycm"] = os.path.abspath(ncycm)
  665. if timeout: # set timeout
  666. try:
  667. timeout = int(timeout)
  668. except:
  669. timeout = 60
  670. new_config["run_config"][run_target]["timeout"] = timeout
  671. if build_target is not None:
  672. new_config["build_target"] = build_target
  673. if parallel is not None:
  674. new_config["parallel"] = parallel
  675. if make_options:
  676. new_config = merge_config_with_makeopts(new_config, make_options)
  677. return new_config
  678. # merge two config, now is appcfg, another is hwcfg
  679. # hwcfg will overwrite configuration in appcfg
  680. def merge_two_config(appcfg, hwcfg):
  681. if isinstance(appcfg, dict) == True and isinstance(hwcfg, dict) == False:
  682. return appcfg
  683. if isinstance(appcfg, dict) == False and isinstance(hwcfg, dict) == True:
  684. return hwcfg
  685. merged_appcfg = copy.deepcopy(appcfg)
  686. dict_merge(merged_appcfg, hwcfg)
  687. return merged_appcfg
  688. def set_global_variables(config):
  689. global SDK_GLOBAL_VARIABLES
  690. if isinstance(config, dict) == False:
  691. return False
  692. if "global_variables" in config:
  693. dict_merge(SDK_GLOBAL_VARIABLES, config["global_variables"])
  694. print("Using global variables: %s" % SDK_GLOBAL_VARIABLES)
  695. return True
  696. def get_app_runresult(apprst):
  697. if not isinstance(apprst, dict):
  698. return "unknown", "-"
  699. if "type" not in apprst:
  700. return "unknown", "-"
  701. rsttype = apprst["type"]
  702. rstvaluedict = apprst.get("value", dict())
  703. if rstvaluedict and len(rstvaluedict) < 3:
  704. rstval = ""
  705. for key in rstvaluedict:
  706. rstval += "%s : %s;" %(key, rstvaluedict[key])
  707. rstval = rstval.rstrip(';')
  708. else:
  709. rstval = "-"
  710. return rsttype, rstval
  711. def save_execute_csv(result, csvfile):
  712. if isinstance(result, dict) == False:
  713. return False
  714. csvlines = ["App, buildstatus, runstatus, buildtime, runtime, type, value, total, text, data, bss"]
  715. for app in result:
  716. size = result[app]["size"]
  717. app_status = result[app]["status"]
  718. app_time = result[app]["time"]
  719. apprsttype, apprstval = get_app_runresult(result[app].get("result", dict()))
  720. csvline ="%s, %s, %s, %s, %s, %s, %s, %d, %d, %d, %d" % (app, app_status["build"], \
  721. app_status.get("run", False), app_time.get("build", "-"), app_time.get("run", "-"), \
  722. apprsttype, apprstval, size["total"], size["text"], size["data"], size["bss"])
  723. csvlines.append(csvline)
  724. display = get_sdk_verb_buildmsg()
  725. save_csv(csvfile, csvlines, display)
  726. return True
  727. def save_bench_csv(result, csvfile):
  728. if isinstance(result, dict) == False:
  729. return False
  730. csvlines = ["App, case, buildstatus, runstatus, buildtime, runtime, type, value, total, text, data, bss"]
  731. for app in result:
  732. appresult = result[app]
  733. for case in appresult:
  734. size = appresult[case]["size"]
  735. app_status = appresult[case]["status"]
  736. app_time = appresult[case]["time"]
  737. apprsttype, apprstval = get_app_runresult(appresult[case].get("result", dict()))
  738. csvline = "%s, %s, %s, %s, %s, %s, %s, %s, %d, %d, %d, %d" % (app, case, app_status["build"], \
  739. app_status.get("run", False), app_time.get("build", "-"), app_time.get("run", "-"), \
  740. apprsttype, apprstval, size["total"], size["text"], size["data"], size["bss"])
  741. csvlines.append(csvline)
  742. # save csv file
  743. display = get_sdk_verb_buildmsg()
  744. save_csv(csvfile, csvlines, display)
  745. return True
  746. def find_local_appconfig(appdir, localcfgs):
  747. if isinstance(appdir, str) and isinstance(localcfgs, dict):
  748. if appdir in localcfgs:
  749. return appdir
  750. else:
  751. foundcfg = None
  752. for localcfg in localcfgs:
  753. localcfgtp = localcfg.strip('/')
  754. striped_dir = appdir.split(localcfgtp, 1)
  755. if len(striped_dir) == 2:
  756. striped_dir = striped_dir[1]
  757. else:
  758. striped_dir = appdir
  759. if striped_dir != appdir:
  760. if striped_dir.startswith('/'):
  761. if foundcfg is None:
  762. foundcfg = localcfg
  763. else:
  764. if len(foundcfg) < len(localcfg):
  765. foundcfg = localcfg
  766. return foundcfg
  767. else:
  768. return None
  769. def fix_evalsoc_verilog_ncycm(verilog):
  770. if os.path.isfile(verilog) == False:
  771. return ""
  772. vfct = ""
  773. with open(verilog, "r", errors='ignore') as vf:
  774. for line in vf.readlines():
  775. line = line.replace("@80", "@00").replace("@90", "@08")
  776. vfct += line
  777. verilog_new = verilog + ".ncycm"
  778. with open(verilog_new, "w") as vf:
  779. vf.write(vfct)
  780. return verilog_new
  781. PROGRAM_UNKNOWN="unknown"
  782. PROGRAM_BAREBENCH="barebench"
  783. PROGRAM_COREMARK="coremark"
  784. PROGRAM_DHRYSTONE="dhrystone"
  785. PROGRAM_WHETSTONE="whetstone"
  786. def parse_benchmark_compatiable(lines):
  787. result = None
  788. program_type = PROGRAM_UNKNOWN
  789. subtype = PROGRAM_UNKNOWN
  790. try:
  791. for line in lines:
  792. # Coremark
  793. if "CoreMark" in line:
  794. program_type = PROGRAM_BAREBENCH
  795. subtype = PROGRAM_COREMARK
  796. if "Iterations*1000000/total_ticks" in line:
  797. value = line.split("=")[1].strip().split()[0]
  798. result = dict()
  799. result["CoreMark/MHz"] = strtofloat(value)
  800. # Dhrystone
  801. if "Dhrystone" in line:
  802. program_type = PROGRAM_BAREBENCH
  803. subtype = PROGRAM_DHRYSTONE
  804. if "1000000/(User_Cycle/Number_Of_Runs)" in line:
  805. value = line.split("=")[1].strip().split()[0]
  806. result = dict()
  807. result["DMIPS/MHz"] = strtofloat(value)
  808. # Whetstone
  809. if "Whetstone" in line:
  810. program_type = PROGRAM_BAREBENCH
  811. subtype = PROGRAM_WHETSTONE
  812. if "MWIPS/MHz" in line:
  813. value = line.split("MWIPS/MHz")[-1].strip().split()[0]
  814. result = dict()
  815. result["MWIPS/MHz"] = strtofloat(value)
  816. except:
  817. return program_type, subtype, result
  818. return program_type, subtype, result
  819. def parse_benchmark_baremetal(lines):
  820. result = None
  821. program_type = PROGRAM_UNKNOWN
  822. subtype = PROGRAM_UNKNOWN
  823. try:
  824. unit = "unknown"
  825. for line in lines:
  826. stripline = line.strip()
  827. if "csv," in stripline.lower():
  828. csv_values = stripline.split(',')
  829. if len(csv_values) >= 3:
  830. key = csv_values[1].strip()
  831. value = csv_values[-1].strip()
  832. if key.lower() == "benchmark":
  833. program_type = PROGRAM_BAREBENCH
  834. unit = value
  835. else:
  836. subtype = key.lower()
  837. result = dict()
  838. result[unit] = strtofloat(value)
  839. break
  840. except:
  841. return program_type, subtype, result
  842. return program_type, subtype, result
  843. def parse_benchmark_baremetal_csv(lines):
  844. result = None
  845. program_type = PROGRAM_UNKNOWN
  846. try:
  847. result = dict()
  848. for line in lines:
  849. stripline = line.strip()
  850. if "csv," in stripline.lower():
  851. csv_values = stripline.split(',')
  852. if len(csv_values) >= 3:
  853. key = csv_values[1].strip()
  854. value = csv_values[-1].strip()
  855. if "BENCH" not in key.upper():
  856. result[key] = value
  857. except:
  858. return program_type, result
  859. return program_type, result
  860. def find_index(key, arr):
  861. try:
  862. index = arr.index(key)
  863. except:
  864. index = -1
  865. return index
  866. def parse_benchmark_runlog(lines, lgf=""):
  867. if isinstance(lines, list) == False:
  868. return PROGRAM_UNKNOWN, PROGRAM_UNKNOWN, None
  869. if len(lines) == 0:
  870. return PROGRAM_UNKNOWN, PROGRAM_UNKNOWN, None
  871. subtype = ""
  872. if lgf.strip() == "": # old style
  873. program_type, subtype, result = parse_benchmark_compatiable(lines)
  874. else:
  875. lgf = lgf.replace("\\", "/")
  876. appnormdirs = os.path.dirname(os.path.normpath(lgf)).replace('\\', '/').split('/')
  877. if "baremetal/benchmark" in lgf:
  878. # baremetal benchmark
  879. program_type, subtype, result = parse_benchmark_baremetal(lines)
  880. if program_type == PROGRAM_UNKNOWN:
  881. # fallback to previous parser
  882. program_type, subtype, result = parse_benchmark_compatiable(lines)
  883. elif "baremetal/demo_dsp" in lgf:
  884. program_type, result = parse_benchmark_baremetal_csv(lines)
  885. program_type = "demo_dsp"
  886. elif "DSP/Examples/RISCV" in lgf:
  887. program_type, result = parse_benchmark_baremetal_csv(lines)
  888. program_type = "nmsis_dsp_example"
  889. index = find_index("RISCV", appnormdirs)
  890. if index >= 0:
  891. subtype = appnormdirs[index + 1]
  892. elif "DSP/Test" in lgf:
  893. program_type, result = parse_benchmark_baremetal_csv(lines)
  894. program_type = "nmsis_dsp_tests"
  895. index = find_index("Test", appnormdirs)
  896. if index >= 0:
  897. subtype = appnormdirs[index + 1]
  898. elif "NN/Examples/RISCV" in lgf:
  899. program_type, result = parse_benchmark_baremetal_csv(lines)
  900. program_type = "nmsis_nn_example"
  901. index = find_index("RISCV", appnormdirs)
  902. if index >= 0:
  903. subtype = appnormdirs[index + 1]
  904. elif "NN/Tests" in lgf:
  905. program_type, result = parse_benchmark_baremetal_csv(lines)
  906. if "full" in appnormdirs:
  907. program_type = "nmsis_nn_test_full"
  908. subtype = "full"
  909. else:
  910. program_type = "nmsis_nn_test_percase"
  911. index = find_index("percase", appnormdirs)
  912. if index >= 0:
  913. subtype = appnormdirs[index + 1]
  914. else:
  915. program_type, subtype, result = parse_benchmark_compatiable(lines)
  916. return program_type, subtype, result
  917. def parse_benchmark_use_pyscript(lines, lgf, pyscript):
  918. if isinstance(lines, list) == False:
  919. return PROGRAM_UNKNOWN, PROGRAM_UNKNOWN, None
  920. if len(lines) == 0:
  921. return PROGRAM_UNKNOWN, PROGRAM_UNKNOWN, None
  922. # function should named parse_benchmark
  923. # function argument and return like parse_benchmark_runlog
  924. parsefunc = import_function("parse_benchmark", pyscript)
  925. if parsefunc is None:
  926. return PROGRAM_UNKNOWN, PROGRAM_UNKNOWN, None
  927. try:
  928. program_type, subtype, result = parsefunc(lines, lgf)
  929. return program_type, subtype, result
  930. except Exception as exc:
  931. print("ERROR: Parse using %s script error: %s" %(pyscript, exc))
  932. return PROGRAM_UNKNOWN, PROGRAM_UNKNOWN, None
  933. def check_tool_exist(tool):
  934. exist = False
  935. if sys.platform == 'win32':
  936. if os.system("where %s" % (tool)) == 0:
  937. exist = True
  938. else:
  939. if os.system("which %s" % (tool)) == 0:
  940. exist = True
  941. return exist
  942. def find_vivado_cmd():
  943. for vivado_cmd in ("vivado", "vivado_lab"):
  944. if sys.platform == 'win32':
  945. if os.system("where %s" % (vivado_cmd)) == 0:
  946. return vivado_cmd
  947. else:
  948. if os.system("which %s" % (vivado_cmd)) == 0:
  949. return vivado_cmd
  950. return None
  951. def program_fpga(bit, target):
  952. if os.path.isfile(bit) == False:
  953. print("Can't find bitstream in %s" % (bit))
  954. return False
  955. print("Try to program fpga bitstream %s to target board %s" % (bit, target))
  956. sys.stdout.flush()
  957. vivado_cmd = find_vivado_cmd()
  958. # check vivado is found or not
  959. if vivado_cmd == None:
  960. print("vivado is not found in PATH, please check!")
  961. return False
  962. tcl = os.path.join(os.path.dirname(os.path.realpath(__file__)), "program_bit.tcl")
  963. target = "*%s" % (target)
  964. progcmd = "%s -mode batch -nolog -nojournal -source %s -tclargs %s %s" % (vivado_cmd, tcl, bit, target)
  965. tmout = get_sdk_fpga_prog_tmout()
  966. if sys.platform != 'win32' and tmout is not None and tmout.strip() != "":
  967. print("Timeout %s do fpga program" % (tmout))
  968. progcmd = "timeout --foreground -s SIGKILL %s %s" % (tmout, progcmd)
  969. print("Do fpga program using command: %s" % (progcmd))
  970. sys.stdout.flush()
  971. ret = os.system(progcmd)
  972. sys.stdout.flush()
  973. if ret != 0:
  974. print("Program fpga bit failed, error code %d" % ret)
  975. return False
  976. print("Program fpga bit successfully")
  977. return True
  978. def find_fpgas():
  979. vivado_cmd = find_vivado_cmd()
  980. if vivado_cmd == None:
  981. print("vivado is not found in PATH, please check!")
  982. return dict()
  983. tcl = os.path.join(os.path.dirname(os.path.realpath(__file__)), "find_devices.tcl")
  984. sys.stdout.flush()
  985. tmp_log = tempfile.mktemp()
  986. os.system("%s -mode batch -nolog -nojournal -source %s -notrace > %s" % (vivado_cmd, tcl, tmp_log))
  987. sys.stdout.flush()
  988. fpgadevices = dict()
  989. with open(tmp_log, "r", errors='ignore') as tf:
  990. for line in tf.readlines():
  991. line = line.strip()
  992. if line.startswith("CSV,") == False:
  993. continue
  994. splits = line.split(",")
  995. if len(splits) != 3:
  996. continue
  997. fpga_serial = "/".join(splits[1].split("/")[2:])
  998. fpgadevices[fpga_serial] = splits[2].strip()
  999. return fpgadevices
  1000. def check_serial_port(serport):
  1001. if serport in find_possible_serports():
  1002. return True
  1003. return False
  1004. def modify_openocd_cfg(cfg, ftdi_serial):
  1005. cfg_bk = cfg + ".backup"
  1006. if (os.path.isfile(cfg)) == False:
  1007. return False
  1008. if os.path.isfile(cfg_bk) == True:
  1009. print("Restore openocd cfg %s" %(cfg))
  1010. shutil.copyfile(cfg_bk, cfg)
  1011. else:
  1012. print("Backup openocd cfg %s" %(cfg))
  1013. shutil.copyfile(cfg, cfg_bk)
  1014. found = False
  1015. contents = []
  1016. index = 0
  1017. with open(cfg, 'r', errors='ignore') as cf:
  1018. contents = cf.readlines()
  1019. for line in contents:
  1020. if line.strip().startswith("transport select"):
  1021. found = True
  1022. break
  1023. index += 1
  1024. if found == False:
  1025. return False
  1026. if sys.platform == 'win32':
  1027. ftdi_serial = "%sA" % (ftdi_serial)
  1028. contents.insert(index, "ftdi_serial %s\ntcl_port disabled\ntelnet_port disabled\n" %(ftdi_serial))
  1029. with open(cfg, 'w') as cf:
  1030. contents = "".join(contents)
  1031. cf.write(contents)
  1032. return True
  1033. GL_CPUCFGs = os.path.join(SCRIPT_DIR, "configs", "cpu")
  1034. def gen_runcfg(cpucfg, runcfg, buildconfig=dict()):
  1035. _, cpucfgdict = load_json(cpucfg)
  1036. _, runcfgdict = load_json(runcfg)
  1037. if cpucfgdict is None:
  1038. return { "build_configs": { "default": {} } }
  1039. if runcfgdict is None:
  1040. return cpucfgdict
  1041. matrixcfgs = runcfgdict.get("matrix", None)
  1042. expectedcfg = runcfgdict.get("expected", dict())
  1043. expectedscfg = runcfgdict.get("expecteds", dict())
  1044. finalruncfg = copy.deepcopy(cpucfgdict)
  1045. # merge buildconfig
  1046. finalruncfg["build_config"] = merge_two_config(finalruncfg.get("build_config", dict()), buildconfig)
  1047. finalruncfg["expected"] = merge_two_config(finalruncfg.get("expected", dict()), expectedcfg)
  1048. finalruncfg["expecteds"] = merge_two_config(finalruncfg.get("expecteds", dict()), expectedscfg)
  1049. if matrixcfgs is None:
  1050. return finalruncfg
  1051. bcfgs = cpucfgdict.get("build_configs", dict())
  1052. newbcfgs = dict()
  1053. for bkey in bcfgs:
  1054. for key in matrixcfgs:
  1055. cfgkey = "%s-%s" % (bkey, key)
  1056. newbcfgs[cfgkey] = merge_two_config(bcfgs[bkey], matrixcfgs[key])
  1057. if len(newbcfgs) > 1:
  1058. finalruncfg["build_configs"] = newbcfgs
  1059. else:
  1060. finalruncfg["build_configs"] = bcfgs
  1061. return finalruncfg
  1062. def gen_coreruncfg(core, runcfg, choice="mini", buildconfig=dict(), casedir=None):
  1063. cpucfgsloc = os.path.join(GL_CPUCFGs, choice)
  1064. if casedir is not None:
  1065. tmp = os.path.join(casedir, choice)
  1066. if os.path.isdir(tmp) == True:
  1067. cpucfgsloc = os.path.realpath(tmp)
  1068. print("Use cpu configs in location %s directory" % (cpucfgsloc))
  1069. cpucfg = os.path.join(cpucfgsloc, "%s.json" % (core))
  1070. return gen_runcfg(cpucfg, runcfg, buildconfig)
  1071. def gen_coreruncfg_custom(core, runcfg, customcfgdir, buildconfig=dict()):
  1072. cpucfg = os.path.join(customcfgdir, "%s.json" % (core))
  1073. return gen_runcfg(cpucfg, runcfg, buildconfig)
  1074. def gen_runyaml(core, locs, fpga_serial, ftdi_serial, cycm, fpgabit, boardtype, ocdcfg, appcfg, hwcfg):
  1075. runyaml = { "runcfg": {"runner": "fpga"},
  1076. "fpga_runners": { core: {
  1077. "board_type": boardtype, "fpga_serial": fpga_serial,
  1078. "ftdi_serial": ftdi_serial, "serial_port": ""}
  1079. },
  1080. "ncycm_runners": { core: {
  1081. "model": cycm if cycm else "" }
  1082. },
  1083. "configs": { core: {
  1084. "fpga": boardtype, "bitstream": fpgabit,
  1085. "ncycm": core, "openocd_cfg": ocdcfg,
  1086. "appcfg": appcfg, "hwcfg": hwcfg }
  1087. },
  1088. "environment": {
  1089. "fpgaloc": locs.get("fpgaloc", ""),
  1090. "ncycmloc": locs.get("ncycmloc", ""),
  1091. "cfgloc": locs.get("cfgloc", "")
  1092. }
  1093. }
  1094. if cycm is not None:
  1095. runyaml["runcfg"]["runner"] = "ncycm"
  1096. return runyaml