diff --git a/doc/microblaze_boot_test_flow.pu b/doc/microblaze_boot_test_flow.pu new file mode 100644 index 00000000..e9505daf --- /dev/null +++ b/doc/microblaze_boot_test_flow.pu @@ -0,0 +1,44 @@ +@startuml Microblaze Boot and Test Flow +start +:Boot Downloader; +partition "Boot Downloader (MicroBlaze)" { + :Check if source is "artifactory"; + :Set URL template for boot partition; + :Download required files (simpleImage.strip, system_top.bit); + :Transfer files to target machine (machine02); +} +:Netbox - Add Parameter; +:Generate Config Template; + +partition "Load Bootfile (MicroBlaze)" { + :source /opt/Xilinx/Vivado/2021.1/settings64.sh; + :cd to project folder; + :Run xsdb; + :connect; + :power on board; + :targets (check device); + :target (select board target); + :fpga -f system_top.bit; + :targets; + :target (select microblaze target); + :dow simpleImage.strip; + :Open UART terminal (2nd terminal); + :screen 115200; + :Record log; + :con (continue microblaze in xsdb); + :enter; + :root; + :analog; +} + +:Linux Tests; +partition linux { + :dmesg | grep sysid; + :dmesg | grep failed; + :dmesg | grep error; + :iio_info; + :iio_info | grep iio:device; + :ifconfig; +} +stop +@enduml diff --git a/doc/microblaze_downloader_flow.pu b/doc/microblaze_downloader_flow.pu new file mode 100644 index 00000000..1938a23c --- /dev/null +++ b/doc/microblaze_downloader_flow.pu @@ -0,0 +1,17 @@ +@startuml MicroBlaze Downloader Flow +start +:Check if source is "artifactory"; +:Prepare URL template for boot partition; +:Check if microblaze is True; +if (microblaze?) then (yes) + :Set design_source_root to microblaze_images/{design_name}; + :Download simpleImage.strip using _get_file; + :Download system_top.bit using _get_file; + :Files saved to outs directory; + :(Optional) Fetch build info and git SHA; + stop +else (no) + :[Other flow not shown]; + stop +endif +@enduml diff --git a/nebula/downloader.py b/nebula/downloader.py index 711129ad..43752492 100644 --- a/nebula/downloader.py +++ b/nebula/downloader.py @@ -156,47 +156,61 @@ def get_gitsha( yaml.dump(bootpartition, f) -def gen_url(ip, branch, folder, filename, addl, url_template, source="artifactory"): - if source == "cloudsmith": - return None - elif source == "artifactory": - if branch == "main": +def gen_url(ip, branch, folder, filename, addl, url_template): + if ( + branch == "main" + and "boot_partition" in url_template + and "microblaze_images" in str(folder) + ): + url = url_template.format(ip, branch, "", "") + folder = get_newest_folder(listFD(url[:-1])) + "/" + str(folder) + print(f"debug: microblaze folder constructed: {folder}") + return url_template.format(ip, branch, folder, filename) + + if branch == "main": + if bool(re.search("boot_partition", url_template)): + url = url_template.format(ip, branch, "", "") + # folder = BUILD_DATE/PROJECT_FOLDER + folder = ( + get_newest_folder(listFD(url[:-1])) + + "/boot_partition/" + + str(folder) + ) + return url_template.format(ip, branch, folder, filename) + elif bool(re.search("hdl", url_template)): + url = url_template.format(ip, addl, "", "") + folder = get_newest_folder(listFD(url[:-1])) + "/" + str(folder) + return url_template.format(ip, addl, folder, filename) + else: + url = url_template.format(ip, "", "") + # folder = BUILD_DATE/PROJECT_FOLDER + folder = get_newest_folder(listFD(url[:-1])) + "/" + str(folder) + return url_template.format(ip, folder, filename) + else: + url = url_template.format(ip, "", "", "") + if branch == "release" or branch == "release_latest": + if bool(re.search("hdl", url_template)): + release_folder = get_latest_release(listFD(url)) + "/" + addl + else: + release_folder = get_latest_release(listFD(url)) + else: if bool(re.search("boot_partition", url_template)): - url = url_template.format(ip, branch, "", "") - # folder = BUILD_DATE/PROJECT_FOLDER - folder = ( - get_newest_folder(listFD(url[:-1])) - + "/boot_partition/" - + str(folder) - ) - return url_template.format(ip, branch, folder, filename) + release_folder = branch.lower() elif bool(re.search("hdl", url_template)): - url = url_template.format(ip, addl, "", "") - folder = get_newest_folder(listFD(url[:-1])) + "/" + str(folder) - return url_template.format(ip, addl, folder, filename) + release_folder = "hdl_" + branch.lower() + "/" + addl else: - url = url_template.format(ip, "", "") - # folder = BUILD_DATE/PROJECT_FOLDER - folder = get_newest_folder(listFD(url[:-1])) + "/" + str(folder) - return url_template.format(ip, folder, filename) - else: - url = url_template.format(ip, "", "", "") - if branch == "release" or branch == "release_latest": - if bool(re.search("hdl", url_template)): - release_folder = get_latest_release(listFD(url)) + "/" + addl - else: - release_folder = get_latest_release(listFD(url)) + release_folder = branch.upper() + url = url_template.format(ip, release_folder, "", "") + # folder = BUILD_DATE/PROJECT_FOLDER + if branch == "main" and "boot_partition" in url_template: + if "microblaze_images" in str(folder): + folder = (get_newest_folder(listFD(url[:-1])) + "/" + str(folder)) + print(f"debug: microblaze folder constructed: {folder}") else: - if bool(re.search("boot_partition", url_template)): - release_folder = branch.lower() - elif bool(re.search("hdl", url_template)): - release_folder = "hdl_" + branch.lower() + "/" + addl - else: - release_folder = branch.upper() - url = url_template.format(ip, release_folder, "", "") - # folder = BUILD_DATE/PROJECT_FOLDER - folder = get_newest_folder(listFD(url[:-1])) + "/" + str(folder) - return url_template.format(ip, release_folder, folder, filename) + folder = ( + get_newest_folder(listFD(url[:-1])) + "/boot_partition/" + str(folder) + ) + return url_template.format(ip, release_folder, folder, filename) def list_files_in_remote_folder(url, source="artifactory"): @@ -603,7 +617,7 @@ def _get_cloudsmith_file( def _get_cloudsmith_headers(self, api_key): """Generate headers for Cloudsmith API requests.""" return { - "Authorization": f"Bearer {api_key}", + "X-Api-Key": api_key, "Accept": "application/json", } @@ -743,6 +757,8 @@ def _get_files_boot_partition( kernel, kernel_root, dt, + design_name=None, + microblaze=False, url_template=None, ): if source == "cloudsmith": @@ -756,67 +772,106 @@ def _get_files_boot_partition( else: url_template = "https://{}/artifactory/sdg-generic-development/boot_partition/{}/{}/{}" - log.info("Getting standard boot files") - # Get kernel - log.info("Getting " + kernel) - self._get_file( - kernel, - source, - kernel_root, - source_root, - branch, - url_template=url_template, - ) + if microblaze: + design_source_root = f"microblaze_images/{design_name}" + print(f"DEBUG: MicroBlaze design_source_root: {design_source_root}") - if boot_subfolder is not None: - design_source_root = os.path.join(reference_boot_folder, boot_subfolder) + # get simpleImage + log.info("Getting simpleimage") + simpleimage = "simpleImage.strip" + self._get_file( + simpleimage, + source, + design_source_root, + source_root, + branch, + url_template=url_template, + ) + # get bitstream + log.info("Getting bitstream") + bitstream = "system_top.bit" + self._get_file( + bitstream, + source, + design_source_root, + source_root, + branch, + url_template=url_template, + ) else: - design_source_root = reference_boot_folder - # Get BOOT.BIN - log.info("Getting BOOT.BIN") - self._get_file( - "BOOT.BIN", - source, - design_source_root, - source_root, - branch, - url_template=url_template, - ) - # Get support files (bootgen_sysfiles.tgz) - log.info("Getting support files") - self._get_file( - "bootgen_sysfiles.tgz", - source, - design_source_root, - source_root, - branch, - url_template=url_template, - ) - # Get device tree - log.info("Getting " + dt) - if devicetree_subfolder is not None: - design_source_root = reference_boot_folder + "/" + devicetree_subfolder - else: - design_source_root = reference_boot_folder - self._get_file( - dt, - source, - design_source_root, - source_root, - branch, - url_template=url_template, - ) - if source == "cloudsmith": - return None - elif source == "artifactory": + log.info("Getting standard boot files") + # Get kernel + log.info("Getting " + kernel) + self._get_file( + kernel, + source, + kernel_root, + source_root, + branch, + url_template=url_template, + ) + + if boot_subfolder is not None: + design_source_root = os.path.join( + reference_boot_folder, boot_subfolder + ) + else: + design_source_root = reference_boot_folder + # Get BOOT.BIN + log.info("Getting BOOT.BIN") + self._get_file( + "BOOT.BIN", + source, + design_source_root, + source_root, + branch, + url_template=url_template, + ) + # Get support files (bootgen_sysfiles.tgz) + log.info("Getting support files") + self._get_file( + "bootgen_sysfiles.tgz", + source, + design_source_root, + source_root, + branch, + url_template=url_template, + ) + + # Get device tree + log.info("Getting " + dt) + if devicetree_subfolder is not None: + design_source_root = ( + reference_boot_folder + "/" + devicetree_subfolder + ) + else: + design_source_root = reference_boot_folder + self._get_file( + dt, + source, + design_source_root, + source_root, + branch, + url_template=url_template, + ) + + if source == "artifactory": # check if info_txt is present - try: - build_info = get_info_txt(url_template) - except Exception as e: - log.warn(e) - build_info = None - get_gitsha(self.url, daily=False, build_info=build_info) + if microblaze: + # skip get_gitsha for microblaze + try: + build_info = get_info_txt(url_template) + except Exception as e: + log.warn(e) + build_info = None + else: + try: + build_info = get_info_txt(url_template) + except Exception as e: + log.warn(e) + build_info = None + get_gitsha(self.url, daily=False, build_info=build_info) def _get_files_hdl(self, hdl_folder, source, source_root, branch, hdl_output=False): design_source_root = hdl_folder @@ -915,42 +970,28 @@ def _get_files_linux( else: url_template = "https://{}/artifactory/sdg-generic-development/linux/releases/{}/{}/{}" - if microblaze: - design_source_root = arch - log.info("Getting simpleimage") - simpleimage = "simpleImage." + design_name + ".strip" - self._get_file( - simpleimage, - source, - design_source_root, - source_root, - branch, - url_template=url_template, - ) - else: - # Get files from linux folder - # Get kernel - log.info("Getting " + kernel) - self._get_file( - kernel, - source, - design_source_root, - source_root, - branch, - url_template=url_template, - ) - # Get device tree - dt_dl = design_name + ".dtb" - log.info("Getting " + dt_dl) - design_source_root = arch - self._get_file( - dt_dl, - source, - design_source_root, - source_root, - branch, - url_template=url_template, - ) + # Get kernel + log.info("Getting " + kernel) + self._get_file( + kernel, + source, + design_source_root, + source_root, + branch, + url_template=url_template, + ) + # Get device tree + dt_dl = design_name + ".dtb" + log.info("Getting " + dt_dl) + design_source_root = arch + self._get_file( + dt_dl, + source, + design_source_root, + source_root, + branch, + url_template=url_template, + ) if source == "artifactory": get_gitsha(self.url, daily=True, linux=True) @@ -1134,19 +1175,19 @@ def _get_files( ) if microblaze: - self._get_files_hdl( - hdl_folder, source, source_root, branch, hdl_output=True - ) - self._get_files_linux( - design_name, + self._get_files_boot_partition( + reference_boot_folder, + devicetree_subfolder, + boot_subfolder, source, source_root, branch, kernel, kernel_root, dt, - arch, + design_name, microblaze, + url_template, ) if rpi: @@ -1172,6 +1213,8 @@ def _get_files( kernel, kernel_root, dt, + design_name, + microblaze, url_template=url_template, ) elif folder == "hdl_linux": @@ -1243,7 +1286,7 @@ def download_boot_files( noos_project = self.no_os_project platform = self.platform - assert design_name in board_configs, "Invalid design name" + assert design_name in board_configs, f"Invalid design name {design_name}" if not firmware: matched = re.match("v[0-1].[0-9][0-9]", branch) diff --git a/nebula/jtag.py b/nebula/jtag.py index 849b5dde..a2b7ae4f 100644 --- a/nebula/jtag.py +++ b/nebula/jtag.py @@ -2,6 +2,7 @@ import os import shutil import subprocess +import sys import time from nebula.common import utils @@ -21,10 +22,12 @@ def __init__( jtag_cable_id=None, jtag_cpu_target_name=None, jtag_connect_retries=3, + jtag_board_target_name=None, ): self.vivado_version = vivado_version self.custom_vivado_path = custom_vivado_path self.jtag_cable_id = jtag_cable_id + self.jtag_board_target_name = jtag_board_target_name self.jtag_cpu_target_name = jtag_cpu_target_name self.jtag_connect_retries = jtag_connect_retries @@ -87,7 +90,6 @@ def run_xsdb(self, cmd): ) cmd = vivado + '; xsdb -eval "{}"'.format(cmd) - # cmd = [vivado + '; xsdb',' -eval "{}"'.format(cmd)] return self._shell_out2(cmd) def restart_board(self): @@ -221,3 +223,32 @@ def full_boot(self): # Must not overwrite memory locations self.run_xsdb(cmd) + + def microblaze_boot_linux(self, bitstream, strip): + """Boot microblaze over JTAG + + Args: + bitstream (str): Path to bitstream file + strip (str): Path to stripped ELF file + + Raises: + Exception: If bitstream or stripped ELF file not found + """ + assert os.path.isfile(bitstream), f"Bitstream file not found: {bitstream}" + assert os.path.isfile(strip), f"Stripped ELF file not found: {strip}" + + cmd = "connect; " + cmd += "after 3000; " + cmd += self.target_set_str(self.jtag_board_target_name) + cmd += "puts {Loading Bitstream}; " + cmd += f"fpga -f {bitstream}; " + cmd += "after 3000; " + cmd += "puts {Loading Stripped ELF}; " + cmd += self.target_set_str(self.jtag_cpu_target_name) + cmd += "targets; " + cmd += "after 3000; " + cmd += f"dow {strip}; " + cmd += "con; " + cmd += "after 3000; " + + self.run_xsdb(cmd) diff --git a/nebula/manager.py b/nebula/manager.py index 5526305e..34be473a 100644 --- a/nebula/manager.py +++ b/nebula/manager.py @@ -33,6 +33,7 @@ def __init__( # noqa:C901 board_name=None, vivado_version=None, extras=None, + microblaze=False, ): # Check if config info exists in yaml self.configfilename = configfilename @@ -184,9 +185,12 @@ def copy_reference_from_sdcard(self, bootbinpath, uimagepath, devtreepath): ref = ref + "/" + str(target) self.monitor[0].copy_reference(ref, target) - def network_check(self): + def network_check(self, microblaze=False): if not self.net.ping_board(): - ip = self.monitor[0].get_ip_address() + if microblaze: + ip = self.monitor[0].get_ip_address_microblaze() + else: + ip = self.monitor[0].get_ip_address() if ip and ip != self.net.dutip: log.info("DUT IP changed to: " + str(ip)) self.net.dutip = ip @@ -196,8 +200,12 @@ def network_check(self): self.configfilename, "network-config", "dutip", ip, self.board_name ) if not ip: - self.monitor[0].request_ip_dhcp() - ip = self.monitor[0].get_ip_address() + if microblaze: + self.monitor[0].request_ip_dhcp_microblaze() + ip = self.monitor[0].get_ip_address_microblaze() + else: + self.monitor[0].request_ip_dhcp() + ip = self.monitor[0].get_ip_address() if not ip: self.monitor[0].stop_log() raise ne.NetworkNotFunctionalAfterBootFileUpdate @@ -434,6 +442,33 @@ def board_reboot_jtag_uart( self.network_check() self.monitor[0].stop_log() + @_release_thread_lock # type: ignore + def board_boot_microblaze_jtag_uart( + self, + system_top_bit_path, + strip_path, + ): + """Reset board and load microblaze bitstream + over JTAG. Then over UART boot + """ + self.monitor[0]._read_until_stop() # Flush + + log.info("Booting microblaze via JTAG") + self._check_files_exist(system_top_bit_path, strip_path) + self.jtag.microblaze_boot_linux(system_top_bit_path, strip_path) + time.sleep(15) + self.monitor[0].stop_log() + self.monitor[0]._wait_for_boot_complete_microblaze() + + self.monitor[0].start_log(logappend=True) + ip = self.monitor[0].get_ip_address_microblaze() + log.info("ip got:" + str(ip)) + if not ip: + self.monitor[0].request_ip_dhcp_microblaze() + ip = self.monitor[0].get_ip_address_microblaze() + self.network_check(microblaze=True) + self.monitor[0].stop_log() + @_release_thread_lock # type: ignore def board_reboot_uart_net_pdu( self, @@ -747,12 +782,14 @@ def run_test(self): for mon in self.monitor: mon.stop_log() - def _find_boot_files(self, folder): + def _find_boot_files(self, folder, microblaze=False): if not os.path.isdir(folder): raise Exception("Boot files folder not found") files = os.listdir(folder) res = [] for file in files: + if file == "properties.yaml": # to skip checking of properties.yaml file + continue path = os.path.join(folder, file) filesize = os.stat(path).st_size if filesize <= 80: @@ -770,6 +807,7 @@ def _find_boot_files(self, folder): targets = { "bit": ["system_top.bit"], + "strip": ["simpleImage.strip"], "bootbin": ["BOOT.BIN", "soc_system.rbf"], "kernel": ["uImage", "Image", "zImage"], "dt": ["devicetree.dtb", "system.dtb", "socfpga.dtb"], @@ -782,7 +820,11 @@ def _find_boot_files(self, folder): "u-boot_xilinx_zynqmp_zcu102_revA.elf", ], } - required = ["bootbin", "dt", "kernel"] + + if microblaze: + required = ["bit", "strip"] + else: + required = ["bootbin", "dt", "kernel"] found_files = {} for filetype in targets.keys(): for pattern in targets[filetype]: @@ -794,20 +836,31 @@ def _find_boot_files(self, folder): raise Exception(f"{filetype} - {pattern} not found") else: found_files.update({filetype: None}) - - return ( - found_files["bit"], - found_files["bootbin"], - found_files["kernel"], - found_files["dt"], - found_files["ext"], - found_files["scr"], - found_files["preloader"], - found_files["uboot"], - ) + if microblaze: + return ( + found_files["bit"], + found_files["strip"], + ) + else: + return ( + found_files["bit"], + found_files["bootbin"], + found_files["kernel"], + found_files["dt"], + found_files["ext"], + found_files["scr"], + found_files["preloader"], + found_files["uboot"], + ) def board_reboot_auto_folder( - self, folder, sdcard=False, design_name=None, recover=False, jtag_mode=False + self, + folder, + sdcard=False, + design_name=None, + recover=False, + jtag_mode=False, + microblaze=False, ): """Automatically select loading mechanism based on current class setup and automatically find boot @@ -833,6 +886,20 @@ def board_reboot_auto_folder( else: log.info("SD-Card/microblaze based device selected") + if microblaze: + files = os.listdir(folder) + if "simpleImage.strip" not in files: + raise Exception("simpleImage.strip not found in folder") + bit, strip = self._find_boot_files(folder, microblaze=True) + log.info("Found microblaze boot files:") + for file in [bit, strip]: + if file: + log.info(file) + self.board_boot_microblaze_jtag_uart( + system_top_bit_path=bit, + strip_path=strip, + ) + return ( bit, bootbin, diff --git a/nebula/network.py b/nebula/network.py index d6afb9bb..15321b94 100644 --- a/nebula/network.py +++ b/nebula/network.py @@ -30,6 +30,7 @@ def __init__( yamlfilename=None, board_name=None, ): + props = ["dutip", "dutusername", "dutpassword", "dhcp", "nic", "nicip"] for prop in props: setattr(self, prop, None) @@ -49,6 +50,10 @@ def __init__( self.ssh_timeout = 30 self.board_name = board_name + self.microblaze_enable = any( + keyword in self.board_name.lower() for keyword in ["vc", "kc"] + ) + def ping_board(self, tries=10): """Ping board and check if any received @@ -66,8 +71,10 @@ def ping_board(self, tries=10): ) out, error = ping.communicate() if "0 received" in str(out): + log.error("Ping failed") raise Exception("Ping failed") pinged = True + log.info("Ping successful") break except Exception: log.warn("Retrying ping") @@ -118,7 +125,6 @@ def check_board_booted(self): def reboot_board(self, bypass_sleep=False): """Reboot board over SSH, otherwise raise exception""" - log.info("Rebooting board over SSH") # Try to reboot board with SSH if possible retries = 3 for t in range(retries): @@ -133,7 +139,7 @@ def reboot_board(self, bypass_sleep=False): time.sleep(30) break else: - # Use PDU + # TODO: Use PDU raise Exception("PDU reset not implemented yet") except Exception as ex: @@ -167,6 +173,7 @@ def run_ssh_command( pty=True, in_stream=False, ) + if result.failed: raise Exception("Failed to run command:", command) @@ -330,7 +337,23 @@ def check_dmesg(self, error_on_warnings=False): tmp_filename_err = "/tmp/" + tmp_filename_root + "_err" tmp_filename_war = "/tmp/" + tmp_filename_root + "_warn" - if self.board_name == "pluto" or self.board_name == "m2k": + if self.microblaze_enable: + max_retries = 3 + for attempt in range(max_retries): + log.info("dmesg command attempt: " + str(attempt + 1)) + try: + self.run_ssh_command("dmesg > " + tmp_filename) + self._dl_file(tmp_filename) + self.run_ssh_command( + 'dmesg | grep -E "failed|error" > ' + tmp_filename_err + ) + self._dl_file(tmp_filename_err) + break + except Exception as e: + log.warning(f"attempt {attempt + 1} failed with exception: {e}") + if attempt == max_retries - 1: + raise e + elif self.board_name == "pluto" or self.board_name == "m2k": with open(tmp_filename_root, "w") as outfile: outfile.write(self.run_ssh_command("dmesg").stdout) with open(tmp_filename_root + "_warn", "w") as outfile: @@ -350,18 +373,21 @@ def check_dmesg(self, error_on_warnings=False): self._dl_file(tmp_filename_err) os.rename(tmp_filename_root, "dmesg.log") - os.rename(tmp_filename_root + "_warn", "dmesg_warn.log") os.rename(tmp_filename_root + "_err", "dmesg_err.log") - logging.info("dmesg logs collected") + warn_log = [] + + if not self.microblaze_enable: + os.rename(tmp_filename_root + "_warn", "dmesg_warn.log") + with open("dmesg_warn.log", "r") as f: + warn_log = f.readlines() - # Process - with open("dmesg.log", "r") as f: - all_log = f.readlines() - with open("dmesg_warn.log", "r") as f: - warn_log = f.readlines() with open("dmesg_err.log", "r") as f: error_log = f.readlines() + with open("dmesg.log", "r") as f: + all_log = f.readlines() + logging.info("dmesg logs collected") + # filtering known errors path = pathlib.Path(__file__).parent.absolute() res = os.path.join(path, "resources", "err_rejects.txt") with open(res) as f: @@ -369,12 +395,10 @@ def check_dmesg(self, error_on_warnings=False): error_rejects_no_ws = [ s.replace(" ", "").replace("\n", "") for s in error_rejects ] - error_log_filetered = [] for i in error_log: msg_log = re.sub(r"^\[[\s\.\d]*\] ", "", i) log_no_ws = msg_log.replace(" ", "").replace("\n", "") - if log_no_ws not in error_rejects_no_ws: error_log_filetered.append(i) @@ -386,7 +410,11 @@ def check_dmesg(self, error_on_warnings=False): if len(error_log_filetered) > 0: log.info("Errors found in dmesg logs") - logs = {"log": all_log, "warn": warn_log, "error": error_log_filetered} + logs = {"log": all_log, "error": error_log_filetered} + if ( + not self.microblaze_enable + ): # subject for change, need to identify what are the warnings and the errors for microblaze + logs.update({"warn": warn_log}) return len(error_log_filetered) > 0, logs def run_diagnostics(self): @@ -412,7 +440,7 @@ def run_diagnostics(self): def verify_checksum(self, file_path, reference, algo="sha256"): if algo == "sha256": - ssh_command = 'python -c "import hashlib;' + ssh_command = 'python3 -c "import hashlib;' ssh_command += f" print(hashlib.sha256(open('{file_path}', 'rb').read()).hexdigest())\"" result = self.run_ssh_command( command=ssh_command, print_result_to_file=False, show_log=False diff --git a/nebula/resources/template_micro_gen.yaml b/nebula/resources/template_micro_gen.yaml new file mode 100644 index 00000000..510fe024 --- /dev/null +++ b/nebula/resources/template_micro_gen.yaml @@ -0,0 +1,264 @@ +# This file is used to generate questions for configuration file generation +# Here is the structure +#
-config: +# field_X: +# name: +# default: (Optional) +# help: +# options: (Optional) +# optional: (depends is used when its a dependent property) +# requires: : (Optional) +# netbox_field: + +board-config: + field_1: + name: board-name + default: max32650_adxl355 + help: "Project name commonly the daughter board connected to an RPI-based carrier" + optional: False + netbox_field: devices.name + field_2: + name: carrier + default: Maxim + help: "Carrier board name" + optional: True + netbox_field: devices.custom_fields.device_carrier + field_3: + name: daughter + help: "Daughter board name" + optional: True + netbox_field: devices.custom_fields.device_daughter + field_4: + name: monitoring-interface + default: uart + help: "Select monitoring interface" + options: [uart, netconsole] + optional: True + netbox_field: devices.custom_fields.monitoring_interface + field_5: + name: allow-jtag + default: False + help: "Allow use of JTAG" + options: ["True", "False"] + optional: False + netbox_field: devices.custom_fields.allow_jtag +network-config: + field_1: + name: dutip + default: + help: "IP address of development board" + optional: False + netbox_field: devices.interfaces.mgmt.ip.address + field_2: + name: dhcp + help: "DHCP network to development board (False assumes static)" + optional: True + options: ["True", "False"] + requires: False:nic,nicip + netbox_field: devices.interfaces.mgmt.custom_fields.dhcp + field_4: + name: nicip + default: 192.168.10.1 + help: "NIC connected to development board ip address" + optional: depends + netbox_field: devices.interfaces.mgmt.custom_fields.nicip + field_5: + name: dutusername + default: analog + help: "Username to access Device" + optional: True + netbox_field: devices.custom_fields.username + field_6: + name: dutpassword + default: analog + help: "Password associated with the username." + optional: True + netbox_field: devices.custom_fields.password +pdu-config: + field_1: + name: pdu_type + default: cyberpower + help: "PDU type" + options: [cyberpower, vesync] + optional: True + requires: vesync:username,password + netbox_field: devices.power_ports.input.pdus[0].device_type.manufacturer.slug + field_2: + name: pduip + default: 192.168.30.2 + help: "IP address of PDU" + optional: True + netbox_field: devices.power_ports.input.pdus[0].primary_ip.address + field_3: + name: outlet + default: 1 + help: "Outlet number on PDU for dev board" + optional: True + type: int + netbox_field: devices.power_ports.input.connected_endpoint.outlet + field_4: + name: username + default: "username" + help: "Username needed for login (only need for vesync so far)" + optional: depends + netbox_field: devices.power_ports.input.pdus[0].custom_fields.username + field_5: + name: password + default: "password" + help: "Password needed for login (only need for vesync so far)" + optional: depends + netbox_field: devices.power_ports.input.pdus[0].custom_fields.password +uart-config: + field_1: + name: address + help: "UART Address" + optional: False + callback: get_uarts + netbox_field: devices.console_ports.UART.label + field_2: + name: baudrate + default: 115200 + help: "UART baudrate" + optional: False + netbox_field: devices.console_ports.UART.custom_fields.baudrate + field_3: + name: logfilename + default: my.log + help: "Output UART logfilename" + optional: False + netbox_field: devices.custom_fields.logfilename + field_4: + name: uboot_done_string + default: zynq-uboot> + help: "Uboot menu prompt" + optional: True + netbox_field: devices.custom_fields.uboot_done_string +system-config: + field_1: + name: tftpserverip + help: "TFTP server address" + optional: True + netbox_field: devices.config_context.tftpserverip + field_2: + name: tftpserverroot + help: "TFTP folder location" + optional: True + netbox_field: devices.config_context.tftpserverroot +driver-config: + field_1: + name: iio_device_names + default: ["adxl355"] + help: "List of IIO devices on board" + optional: False + netbox_field: devices.custom_fields.iio_device_names +downloader-config: + field_1: + name: http_server_ip + help: "IP address of build server with boot files" + optional: True + netbox_field: devices.custom_fields.http_server_ip + field_2: + name: reference_boot_folder + default: vc707_ad6676evb + help: "Folder name where reference boot files exist for specific board. \nThis is the same as the boot folder name of the AD-FMC-SDCARD." + optional: False + netbox_field: devices.custom_fields.reference_boot_folder + field_3: + name: platform + help: "Platform used of the no-os project" + optional: True + netbox_field: devices.custom_fields.platform +netbox-config: + field_1: + name: netbox_server + help: "IP address of the netbox server" + default: 192.168.10.1 + optional: True + netbox_field: devices.config_context.netbox_server + field_2: + name: netbox_server_port + default: 8000 + help: "Port used by the netbox service" + optional: True + netbox_field: devices.config_context.netbox_port + field_3: + name: netbox_base_url + default: netbox + help: "String used as netbox base url. i.e server:port\base_url" + optional: True + netbox_field: devices.config_context.netbox_base_url + field_4: + name: netbox_api_token + default: 0123456789abcdef0123456789abcdef01234567 + help: "Token for netbox rest api access" + optional: True + netbox_field: devices.config_context.netbox_api_token +jtag-config: + field_1: + name: vivado_version + default: 2021.1 + help: "Version of vivado to use" + optional: False + netbox_field: devices.config_context.vivado_version + field_2: + name: custom_vivado_path + default: None + help: "Custom path to vivado including version.\nEx: /opt/Xilinx/Vivado/2021.1\nOverrides vivado_version if set" + optional: True + netbox_field: devices.config_context.custom_vivado_path + field_3: + name: jtag_cable_id + default: 210299A567FE + help: "Substring of JTAG cable ID. Run 'jtag target' through xsdb to get it.\n Just really need code not full name." + optional: False + netbox_field: devices.console_ports.JTAG.label + field_4: + name: jtag_board_target_name + default: xc7vx485t + help: "Name use to identify jtag target\n. This will be used for filtering jtag target.\n Can use wildcards e.x *" + optional: False + netbox_field: devices.custom_fields.jtag_board_target_name + field_5: + name: jtag_cpu_target_name + default: MicroBlaze*#0 + help: "Name use to identify jtag target\n. This will be used for filtering jtag target.\n Can use wildcards e.x *" + optional: True + netbox_field: devices.custom_fields.jtag_cpu_target_name + field_6: + name: jtag_connect_retries + default: 3 + help: "Number of retries for JTAG connection" + optional: False +microblaze-config: + field_1: + name: fpga_bitstream + default: system_top.bit + help: "FPGA bitstream filename to program on KC705" + optional: False + field_2: + name: elf_image + default: simpleImage.strip + help: "Stripped image to load onto MicroBlaze" + optional: False + field_3: + name: xsdb_script + default: None + help: "Optional XSDB TCL script (if provided overrides manual sequence)" + optional: True + field_4: + name: cpu_target_pattern + default: MicroBlaze*#0 + help: "Target pattern for selecting MicroBlaze core in XSDB" + optional: True + field_5: + name: boot_mode + default: jtag + options: [jtag, flash] + help: "Boot/programming method" + optional: True + field_6: + name: auto_continue + default: True + options: ["True", "False"] + help: "Issue 'con' automatically after ELF download" + optional: True diff --git a/nebula/tasks.py b/nebula/tasks.py index b3437a3a..97f6a07e 100644 --- a/nebula/tasks.py +++ b/nebula/tasks.py @@ -900,6 +900,7 @@ def board_diagnostics_manager( "yamlfilename": "Path to yaml config file. Default: /etc/default/nebula", "board_name": "Name of DUT design (Ex: zynq-zc706-adv7511-fmcdaq2). Require for multi-device config files", "sdcard": "Get boot files from the sdcard", + "microblaze": "set this flag for Microblaze boards", }, ) def update_boot_files_manager( @@ -912,9 +913,12 @@ def update_boot_files_manager( yamlfilename="/etc/default/nebula", board_name=None, sdcard=False, + microblaze=False, ): """Update boot files through u-boot menu (Assuming board is running)""" - m = nebula.manager(configfilename=yamlfilename, board_name=board_name) + m = nebula.manager( + configfilename=yamlfilename, board_name=board_name, microblaze=microblaze + ) if not folder: m.board_reboot_auto( @@ -925,7 +929,9 @@ def update_boot_files_manager( sdcard=sdcard, ) else: - m.board_reboot_auto_folder(folder=folder, sdcard=sdcard, design_name=board_name) + m.board_reboot_auto_folder( + folder=folder, sdcard=sdcard, design_name=board_name, microblaze=microblaze + ) @task( @@ -1270,7 +1276,13 @@ def update_boot_files_uart( "board_name": "Name of DUT design (Ex: zynq-zc706-adv7511-fmcdaq2). Require for multi-device config files", } ) -def check_dmesg(c, ip, user="root", password="analog", board_name=None): +def check_dmesg( + c, + ip, + user="root", + password="analog", + board_name=None, +): """Download and parse remote board's dmesg log Three log files will be produced: dmesg.log - Full dmesg diff --git a/nebula/uart.py b/nebula/uart.py index 6b0f1d0c..2a5020d3 100644 --- a/nebula/uart.py +++ b/nebula/uart.py @@ -336,6 +336,7 @@ def _attemp_login(self, username, password): cmd = password self._write_data(cmd) data = self._read_for_time(period=2) + time.sleep(5) # Check cmd = "" self._write_data(cmd) @@ -460,6 +461,63 @@ def get_local_mac_usbdev(self): cmd = "cat /www/index.html | grep '00:' | grep -v `cat /sys/class/net/usb0/address` | sed 's/ *<[^>]*> */ /g'" return self.get_uart_command_for_linux(cmd, "00") + def request_ip_dhcp_microblaze(self, nic="eth0"): + self._read_until_stop() # Flush + self._write_data( + f"ifconfig {nic} down; ifconfig {nic} up; udhcpc -r {nic}; sleep 5" + ) + time.sleep(5) + self._read_until_stop() # Flush + time.sleep(5) + self._write_data(f"udhcpc {nic}; sleep 5") + address = self.get_ip_address_microblaze() + log.debug(f"Got IP address: {address}") + return None + + def get_ip_address_microblaze(self): + """Read IP address of DUT using ifconfig from UART for MicroBlaze""" + cmd = "ifconfig eth0 | grep -v 127 | awk '$1 == \"inet\" {print $2}' | awk -F'/' '{print $1}'" + restart = False + if self.listen_thread_run: + restart = True + self.stop_log() + # Check if we need to login to the console + if not self._check_for_login(): + raise Exception("Console inaccessible due to login failure") + log.info("We seem to be logged in...") + self._write_data(cmd) + data = self._read_for_time(period=2) + log.info("Data read: " + str(data)) + if restart: + self.start_log(logappend=True) + for d in data: + if isinstance(d, list): + for c in d: + if "ifconfig eth0" in c: + continue + c = escape_ansi(c.replace("\r", "")) + c = c.replace("addr:", "").strip() + log.info("Checking: " + str(c)) + try: + ipaddress.ip_address(c) + log.info("Found IP: " + str(c)) + return c + except Exception: + continue + else: + try: + if "ifconfig eth0" in d: + continue + d = escape_ansi(d) + d = d.replace("addr:", "").strip() + log.info("Checking: " + str(d)) + ipaddress.ip_address(d) + log.info("Found IP: " + str(d)) + return d + except Exception: + continue + return None + def get_ip_address(self): """Read IP address of DUT using ip command from UART""" # cmd = "ip -4 addr | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127" @@ -611,6 +669,68 @@ def _wait_for_boot_complete_linaro(self, done_string="Welcome to Linaro 14.04"): self.start_log(logappend=True) return out + def _wait_for_boot_complete_microblaze(self, max_time=10, prompt="#"): + """Wait for MicroBlaze to boot by waiting for prompt, spamming ENTER if needed.""" + + # Spam ENTER until "Welcome to Buildroot" is found or timeout + for _ in range(max_time): + self._write_data("\n") # Spam ENTER + time.sleep(5) + found = self._read_until_done( + done_string="Welcome to Buildroot", max_time=1 + ) + log.info("UART read after ENTER: {}".format(found)) + if found: + log.info( + "---buildroot welcome message found, waiting for login prompt---" + ) + break + time.sleep(1) + else: + log.warning("Buildroot welcome message not found after spamming ENTER") + return False + + # Waiting for login prompt and attempt login + login_success = False + for attempt in range(15): + self._write_data("\n") + time.sleep(1) + lines = self._read_until_stop() + log.debug("UART lines: {}".format(lines)) + for line in lines: + if "buildroot login:" in line: + log.info("Buildroot login prompt found") + login_success = self._check_for_login() + if not login_success: + log.warning("Login failed") + return False + break + if login_success: + break + time.sleep(1) + if not login_success: + log.warning("Login failed") + return False + + # wait for MicroBlaze shell prompt + isRootLevel = False + for attempt in range(5): + self._write_data("\n") + time.sleep(1) + lines = self._read_until_stop() + log.debug("UART lines: {}".format(lines)) + for uart_line in lines: + if prompt in uart_line: + log.info("Microblaze prompt found, boot complete") + isRootLevel = True + break + if isRootLevel: + break + time.sleep(1) + if not isRootLevel: + log.warning("Microblaze prompt not found") + return False + def _enter_uboot_menu_from_power_cycle(self): log.info("Spamming ENTER to get UART console") # stop_at_done = False diff --git a/tests/microblaze_test.py b/tests/microblaze_test.py new file mode 100644 index 00000000..fca285c5 --- /dev/null +++ b/tests/microblaze_test.py @@ -0,0 +1,68 @@ +import os +import shutil +import time + +import pytest + +import nebula +from nebula import downloader, network + +here = os.path.dirname(os.path.abspath(__file__)) +cfg = os.path.join(here, "nebula_config", "microblaze.yaml") +out_folder = os.path.join(os.path.dirname(os.path.dirname(__file__)), "outs") +log_folder = os.path.join(here, "logs") +if not os.path.exists(log_folder): + os.makedirs(log_folder) +manager = nebula.manager( + configfilename=cfg, board_name="kcu105_adrv9371x", microblaze=True +) + + +def test_microblaze_downloader(): + # Clean output folder + if os.path.isdir(out_folder): + shutil.rmtree(out_folder) + + d = downloader(yamlfilename=cfg, board_name="kcu105_adrv9371x") + d.download_boot_files( + "kcu105_adrv9371x", + source="artifactory", + source_root="artifactory.analog.com", + branch="main", + microblaze=True, + ) + + assert os.path.isfile(os.path.join("/root/wk_ace/nebula/outs/", "system_top.bit")) + assert os.path.isfile( + os.path.join("/root/wk_ace/nebula/outs/", "simpleImage.strip") + ) + assert os.path.isfile(os.path.join("/root/wk_ace/nebula/outs/", "hashes.txt")) + # assert os.path.isfile(os.path.join("/root/wk_ace/nebula/outs/", "properties.yaml")) + + +def test_microblaze_boot(): + # Use files from outs folder + bitstream = os.path.join("/root/wk_ace/nebula/outs/", "system_top.bit") + strip = os.path.join("/root/wk_ace/nebula/outs/", "simpleImage.strip") + assert os.path.isfile(bitstream), "Bitstream file not found" + assert os.path.isfile(strip), "Strip file not found" + + manager = nebula.manager(configfilename=cfg, board_name="kcu105_adrv9371x") + manager.monitor[0].logfilename = os.path.join( + log_folder, "kcu105_adrv9371x" + ) # Flush + + # boot microblaze board + manager.board_reboot_auto_folder(out_folder, microblaze=True) + + +# @pytest.mark.stress +def test_microblaze_network(): + # test SSH connection and dmesg check for microblaze + manager.monitor[0].logfilename = os.path.join( + log_folder, "kcu105_adrv9371x" + ) # Flush + net = network( + yamlfilename=cfg, board_name="kcu105_adrv9371x", dutip=manager.net.dutip + ) + net.check_dmesg() diff --git a/tests/nebula_config/microblaze.yaml b/tests/nebula_config/microblaze.yaml new file mode 100644 index 00000000..63c89539 --- /dev/null +++ b/tests/nebula_config/microblaze.yaml @@ -0,0 +1,128 @@ +kc705_ad9467_fmc: + board-config: + - board-name: kc705_ad9467_fmc + - carrier: KC705 + - daughter: AD9467 + - monitoring-interface: uart + - allow-jtag: True + downloader-config: + - reference_boot_folder: kc705_ad9467_fmc + - platform: Xilinx + driver-config: + - iio_device_names: + - ad9517-4 + - cf-ad9467-core-lpc + jtag-config: + - vivado_version: 2021.1 + - jtag_cable_id: 210203859085A + - jtag_board_target_name: xc7k325t + - jtag_cpu_target_name: MicroBlaze*#0 + - jtag_connect_retries: 3 + microblaze-config: + - microblaze_enable: True + - fpga_bitstream: system_top.bit + - elf_image: simpleImage.strip + netbox-config: + - netbox_server: primary.englab + - netbox_server_port: '8000' + - netbox_base_url: netbox + - netbox_api_token: 4d6a33918d555e6d1e5539b9e3329d3ccb1bc6a5 + network-config: + - dutip: 192.168.10.238 + pdu-config: + - pdu_type: cyberpower + - pduip: 192.168.10.25 + - outlet: '4' + - username: cyber + - password: cyber1 + uart-config: + - address: /dev/serial/by-path/pci-0000:00:14.0-usb-0:6.4:1.0-port0 + - baudrate: '115200' + - logfilename: kc705_ad9467_fmc_iio.log +kcu105_adrv9371x: + board-config: + - board-name: kcu105_adrv9371x + - carrier: KCU105 + - daughter: ADRV9371 + - monitoring-interface: uart + - allow-jtag: True + downloader-config: + - reference_boot_folder: kcu105_adrv9371x + - platform: Xilinx + driver-config: + - iio_device_names: + - ad7291 + - ad9528-1 + - ad9371-phy + - axi-ad9371-rx-obs-hpc + - axi-ad9371-tx-hpc + - axi-ad9371-rx-hpc + jtag-config: + - vivado_version: 2021.1 + - jtag_cable_id: 210308AE6A92 + - jtag_board_target_name: xcku040 + - jtag_cpu_target_name: MicroBlaze*#0 + - jtag_connect_retries: 3 + microblaze-config: + - microblaze_enable: True + - fpga_bitstream: system_top.bit + - elf_image: simpleImage.strip + netbox-config: + - netbox_server: primary.englab + - netbox_server_port: '8000' + - netbox_base_url: netbox + - netbox_api_token: 4d6a33918d555e6d1e5539b9e3329d3ccb1bc6a5 + network-config: + - dutip: 192.168.10.237 + pdu-config: + - pdu_type: cyberpower + - pduip: 192.168.10.25 + - outlet: '3' + - username: cyber + - password: cyber1 + uart-config: + - address: /dev/serial/by-path/pci-0000:00:14.0-usb-0:6.2:1.1-port0 + - baudrate: '115200' + - logfilename: kcu105_adrv9371x_iio.log +vc707_fmcomms2-3: + board-config: + - board-name: vc707_fmcomms2-3 + - carrier: VC707 + - daughter: FMCOMMS3 + - monitoring-interface: uart + - allow-jtag: True + downloader-config: + - reference_boot_folder: vc707_ad6676evb + - platform: Xilinx + driver-config: + - iio_device_names: + - ad9361-phy + - cf-ad9361-dds-core-lpc + - cf-ad9361-lpc + jtag-config: + - vivado_version: 2021.1 + - jtag_cable_id: 210203A25426A + - jtag_board_target_name: xc7vx485t + - jtag_cpu_target_name: MicroBlaze*#0 + - jtag_connect_retries: 3 + microblaze-config: + - microblaze_enable: True + - fpga_bitstream: system_top.bit + - elf_image: simpleImage.strip + netbox-config: + - netbox_server: primary.englab + - netbox_server_port: '8000' + - netbox_base_url: netbox + - netbox_api_token: 4d6a33918d555e6d1e5539b9e3329d3ccb1bc6a5 + network-config: + - dutip: 192.168.10.239 + pdu-config: + - pdu_type: cyberpower + - pduip: 192.168.10.24 + - outlet: '6' + - username: cyber + - password: cyber + uart-config: + - address: /dev/serial/by-path/pci-0000:00:14.0-usb-0:6.1.1:1.0-port0 + - baudrate: '115200' + - logfilename: vc707_fmcomms2-3_iio.log diff --git a/tests/test_downloader.py b/tests/test_downloader.py index 2c45a4d8..06cadde3 100644 --- a/tests/test_downloader.py +++ b/tests/test_downloader.py @@ -119,20 +119,44 @@ def test_noos_downloader(test_downloader, board_name, branch, filetype): assert os.path.isfile("outs/hashes.txt") -@pytest.mark.skip(reason="Not built") -@pytest.mark.parametrize("board_name", ["kc705_fmcomms4"]) -@pytest.mark.parametrize("branch", ["release", "main"]) +# @pytest.mark.skip(reason="Not built") #remove for testing +@pytest.mark.parametrize("board_name", ["kcu105_adrv9371x"]) +@pytest.mark.parametrize("branch", ["main"]) @pytest.mark.parametrize("filetype", ["microblaze"]) def test_microblaze_downloader(test_downloader, board_name, branch, filetype): - test_downloader(board_name, branch, filetype) - try: - assert os.path.isfile("outs/system_top.hdf") - except Exception: - assert os.path.isfile("outs/system_top.xsa") - assert os.path.isfile("outs/simpleImage.kc705_fmcomms4.strip") + import sys + + import nebula + + print(f"DEBUG: Python executable: {sys.executable}") + print(f"DEBUG: Nebula location: {nebula.__file__}") + # Remove the test_downloader call and use microblaze.yaml directly + microblaze_yaml = os.path.join( + os.path.dirname(__file__), "nebula_config", "microblaze.yaml" + ) + + if os.path.isdir("outs"): + shutil.rmtree("outs") + + d = downloader(yamlfilename=microblaze_yaml, board_name=board_name) + d.download_boot_files( + board_name, + source="artifactory", + source_root="artifactory.analog.com", + branch=branch, + microblaze=True, + ) + + # assert os.path.isfile("outs/system_top.xsa") # HDL file + assert os.path.isfile("outs/system_top.bit") # Bitstream + assert os.path.isfile("outs/simpleImage.strip") # MicroBlaze kernel assert os.path.isfile("outs/properties.yaml") assert os.path.isfile("outs/hashes.txt") + # Cleanup + if os.path.isdir("outs"): + shutil.rmtree("outs") + @pytest.mark.parametrize("board_name", ["eval-adxrs290-pmdz"]) @pytest.mark.parametrize("branch", ["rpi-6.6.y"])