From 7f112f1aea65ac5ccad97e9ab00e2c0c669bb02b Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Thu, 5 Mar 2026 14:57:25 +0800 Subject: [PATCH 01/28] Fixed url generated path for branch release downloader Signed-off-by: Zvckkk --- nebula/downloader.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nebula/downloader.py b/nebula/downloader.py index 711129ad..b17c914e 100644 --- a/nebula/downloader.py +++ b/nebula/downloader.py @@ -195,7 +195,11 @@ def gen_url(ip, branch, folder, filename, addl, url_template, source="artifactor 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) + folder = ( + get_newest_folder(listFD(url[:-1])) + + "/boot_partition/" + + str(folder) + ) return url_template.format(ip, release_folder, folder, filename) From b3b299f6d768356a0070c46ae0a69662bb929e00 Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Thu, 5 Mar 2026 16:44:33 +0800 Subject: [PATCH 02/28] update file verification to skip properties.yaml Signed-off-by: Zvckkk --- nebula/manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nebula/manager.py b/nebula/manager.py index 5526305e..0d93fd21 100644 --- a/nebula/manager.py +++ b/nebula/manager.py @@ -753,6 +753,8 @@ def _find_boot_files(self, folder): 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: @@ -963,3 +965,4 @@ def verify_checksum(self, folder): self.net.verify_checksum( file_path=os.path.join("/boot", fname), reference=hash ) + From 64da0a6a5089d34570dac7a490059bce634835b6 Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Thu, 5 Mar 2026 16:54:58 +0800 Subject: [PATCH 03/28] fixed lints Signed-off-by: Zvckkk --- nebula/downloader.py | 6 ++---- nebula/manager.py | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/nebula/downloader.py b/nebula/downloader.py index b17c914e..20f593c3 100644 --- a/nebula/downloader.py +++ b/nebula/downloader.py @@ -196,10 +196,8 @@ def gen_url(ip, branch, folder, filename, addl, url_template, source="artifactor url = url_template.format(ip, release_folder, "", "") # folder = BUILD_DATE/PROJECT_FOLDER folder = ( - get_newest_folder(listFD(url[:-1])) - + "/boot_partition/" - + str(folder) - ) + get_newest_folder(listFD(url[:-1])) + "/boot_partition/" + str(folder) + ) return url_template.format(ip, release_folder, folder, filename) diff --git a/nebula/manager.py b/nebula/manager.py index 0d93fd21..c6ea5fae 100644 --- a/nebula/manager.py +++ b/nebula/manager.py @@ -753,7 +753,7 @@ def _find_boot_files(self, folder): files = os.listdir(folder) res = [] for file in files: - if file == "properties.yaml": #to skip checking of properties.yaml file + if file == "properties.yaml": # to skip checking of properties.yaml file continue path = os.path.join(folder, file) filesize = os.stat(path).st_size @@ -965,4 +965,3 @@ def verify_checksum(self, folder): self.net.verify_checksum( file_path=os.path.join("/boot", fname), reference=hash ) - From 4cad51df6ae024b73a5411f84ea1ecbc522bf84a Mon Sep 17 00:00:00 2001 From: Ace Alexander Ilog Date: Fri, 6 Mar 2026 10:03:58 +0800 Subject: [PATCH 04/28] update ssh python command in checksum verification files Signed-off-by: Ace Alexander Ilog --- nebula/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nebula/network.py b/nebula/network.py index d6afb9bb..5708dc4f 100644 --- a/nebula/network.py +++ b/nebula/network.py @@ -412,7 +412,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 From cd501b134a3baf1e5c90cdf19b379523bc4e50bf Mon Sep 17 00:00:00 2001 From: Macy Libed Date: Fri, 7 Nov 2025 11:20:32 +0800 Subject: [PATCH 05/28] removing variant Signed-off-by: Macy Libed --- nebula/resources/template_micro_gen.yaml | 253 +++++++++++++++++++++++ nebula/tasks.py | 2 +- 2 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 nebula/resources/template_micro_gen.yaml diff --git a/nebula/resources/template_micro_gen.yaml b/nebula/resources/template_micro_gen.yaml new file mode 100644 index 00000000..65b58be1 --- /dev/null +++ b/nebula/resources/template_micro_gen.yaml @@ -0,0 +1,253 @@ +# 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: 192.168.10.2 + 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: True + 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: True + netbox_field: devices.console_ports.JTAG.label + field_4: + 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 +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 \ No newline at end of file diff --git a/nebula/tasks.py b/nebula/tasks.py index b3437a3a..854555a3 100644 --- a/nebula/tasks.py +++ b/nebula/tasks.py @@ -762,7 +762,7 @@ def gen_config_netbox( netbox_baseurl=None, jenkins_agent=None, board_name=None, - include_variants=True, + include_variants=False, include_children=True, devices_status="active", devices_role="fpga-dut", From e27a56e492f3b03269d55f9bfa3f53dd872ee001 Mon Sep 17 00:00:00 2001 From: Ace Alexander Ilog Date: Fri, 7 Nov 2025 13:23:54 +0800 Subject: [PATCH 06/28] microblaze bootfiles downloader update --- nebula/downloader.py | 46 ++++++++++++++--- tests/nebula_config/microblaze.yaml | 79 +++++++++++++++++++++++++++++ tests/test_downloader.py | 38 ++++++++++---- 3 files changed, 146 insertions(+), 17 deletions(-) create mode 100644 tests/nebula_config/microblaze.yaml diff --git a/nebula/downloader.py b/nebula/downloader.py index 20f593c3..27e2bc8e 100644 --- a/nebula/downloader.py +++ b/nebula/downloader.py @@ -196,8 +196,21 @@ def gen_url(ip, branch, folder, filename, addl, url_template, source="artifactor url = url_template.format(ip, release_folder, "", "") # folder = BUILD_DATE/PROJECT_FOLDER folder = ( - get_newest_folder(listFD(url[:-1])) + "/boot_partition/" + str(folder) - ) + get_newest_folder(listFD(url[:-1])) + "/boot_partition/" + str(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: + folder = ( + get_newest_folder(listFD(url[:-1])) + "/boot_partition/" + str(folder) + ) + else: + folder = ( + get_newest_folder(listFD(url[:-1])) + "/boot_partition/" + str(folder) + ) return url_template.format(ip, release_folder, folder, filename) @@ -918,9 +931,14 @@ def _get_files_linux( url_template = "https://{}/artifactory/sdg-generic-development/linux/releases/{}/{}/{}" if microblaze: - design_source_root = arch + if branch == "main": + url_template = ( + "https://{}/artifactory/sdg-generic-development/boot_partition/{}/{}/{}") + design_source_root = f"microblaze_images/{design_name}" + print(f"DEBUG: MicroBlaze design_source_root: {design_source_root}") + # get simpleImage log.info("Getting simpleimage") - simpleimage = "simpleImage." + design_name + ".strip" + simpleimage = "simpleImage.strip" self._get_file( simpleimage, source, @@ -929,6 +947,17 @@ def _get_files_linux( 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: # Get files from linux folder # Get kernel @@ -1136,9 +1165,10 @@ def _get_files( ) if microblaze: - self._get_files_hdl( - hdl_folder, source, source_root, branch, hdl_output=True - ) + hdl_branch = "master" if branch == "main" else branch + #self._get_files_hdl( + # hdl_folder, source, source_root, hdl_branch, hdl_output=True + #) self._get_files_linux( design_name, source, @@ -1245,7 +1275,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/tests/nebula_config/microblaze.yaml b/tests/nebula_config/microblaze.yaml new file mode 100644 index 00000000..9276b535 --- /dev/null +++ b/tests/nebula_config/microblaze.yaml @@ -0,0 +1,79 @@ +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: + - jtag_cable_id: 210308AE6A92 + - jtag_cpu_target_name: MicroBlaze*#0 + microblaze-config: + - 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.2 + 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: + - jtag_cable_id: 210203A25426A + - jtag_cpu_target_name: MicroBlaze*#0 + microblaze-config: + - 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.2 + 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..3fcae032 100644 --- a/tests/test_downloader.py +++ b/tests/test_downloader.py @@ -119,19 +119,39 @@ 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", ["release","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"]) From 124f3b8d010be395cbc328a7177a21fcadeff8e7 Mon Sep 17 00:00:00 2001 From: Ace Alexander Ilog Date: Tue, 11 Nov 2025 09:44:26 +0800 Subject: [PATCH 07/28] moved the microblaze dlbootfiles to _get_files_boot_partition --- nebula/downloader.py | 238 ++++++++++++++++++++++++------------------- 1 file changed, 135 insertions(+), 103 deletions(-) diff --git a/nebula/downloader.py b/nebula/downloader.py index 27e2bc8e..2c2ff0bd 100644 --- a/nebula/downloader.py +++ b/nebula/downloader.py @@ -758,6 +758,8 @@ def _get_files_boot_partition( kernel, kernel_root, dt, + design_name=None, + microblaze=False, url_template=None, ): if source == "cloudsmith": @@ -771,60 +773,87 @@ 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) @@ -930,58 +959,59 @@ def _get_files_linux( else: url_template = "https://{}/artifactory/sdg-generic-development/linux/releases/{}/{}/{}" - if microblaze: - if branch == "main": - url_template = ( - "https://{}/artifactory/sdg-generic-development/boot_partition/{}/{}/{}") - design_source_root = f"microblaze_images/{design_name}" - print(f"DEBUG: MicroBlaze design_source_root: {design_source_root}") - # 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: - # 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, - ) + #if microblaze: + # if branch == "main": + # url_template = ( + # "https://{}/artifactory/sdg-generic-development/boot_partition/{}/{}/{}") + # design_source_root = f"microblaze_images/{design_name}" + # print(f"DEBUG: MicroBlaze design_source_root: {design_source_root}") + # # 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: + + # 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, + ) if source == "artifactory": get_gitsha(self.url, daily=True, linux=True) @@ -1204,6 +1234,8 @@ def _get_files( kernel, kernel_root, dt, + design_name, + microblaze, url_template=url_template, ) elif folder == "hdl_linux": From ee2192f475bf60e604ee58d5ee081a983d70dfdc Mon Sep 17 00:00:00 2001 From: Ace Alexander Ilog Date: Tue, 11 Nov 2025 11:25:24 +0800 Subject: [PATCH 08/28] cleared commit divergence from the original repository --- nebula/downloader.py | 13 ++++++++----- tests/test_downloader.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/nebula/downloader.py b/nebula/downloader.py index 2c2ff0bd..4dc6c7cc 100644 --- a/nebula/downloader.py +++ b/nebula/downloader.py @@ -772,7 +772,7 @@ def _get_files_boot_partition( ) else: url_template = "https://{}/artifactory/sdg-generic-development/boot_partition/{}/{}/{}" - + if microblaze: design_source_root = f"microblaze_images/{design_name}" print(f"DEBUG: MicroBlaze design_source_root: {design_source_root}") @@ -1195,20 +1195,23 @@ def _get_files( ) if microblaze: - hdl_branch = "master" if branch == "main" else branch + #hdl_branch = "master" if branch == "main" else branch #self._get_files_hdl( # hdl_folder, source, source_root, hdl_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: diff --git a/tests/test_downloader.py b/tests/test_downloader.py index 3fcae032..bf5d8613 100644 --- a/tests/test_downloader.py +++ b/tests/test_downloader.py @@ -121,7 +121,7 @@ def test_noos_downloader(test_downloader, board_name, branch, filetype): #@pytest.mark.skip(reason="Not built") #remove for testing @pytest.mark.parametrize("board_name", ["kcu105_adrv9371x"]) -@pytest.mark.parametrize("branch", ["release","main"]) +@pytest.mark.parametrize("branch", ["main"]) @pytest.mark.parametrize("filetype", ["microblaze"]) def test_microblaze_downloader(test_downloader, board_name, branch, filetype): import sys From 3230c65ce4bb559d8224f2990668247b1cb14498 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Tue, 23 Sep 2025 20:27:09 -0400 Subject: [PATCH 09/28] cherry-pick: ab943fc Microblaze Fixes --- nebula/jtag.py | 131 ++++++++++++++++++++++++++++++++++++++++++------- nebula/uart.py | 55 +++++++++++++++++++++ 2 files changed, 168 insertions(+), 18 deletions(-) diff --git a/nebula/jtag.py b/nebula/jtag.py index 849b5dde..79bdd814 100644 --- a/nebula/jtag.py +++ b/nebula/jtag.py @@ -3,6 +3,7 @@ import shutil import subprocess import time +import sys from nebula.common import utils @@ -33,23 +34,25 @@ def __init__( ) # Check target device available - jtag_connected = False - for c in range(self.jtag_connect_retries): - cmd = "connect; after 1000; " + self.target_set_str( - self.jtag_cpu_target_name - ) - jtag_connected = self.run_xsdb(cmd) - if jtag_connected: - log.info( - "JTAG {} connection attempt successful".format(self.jtag_cable_id) - ) - break - log.warning( - "JTAG {} connection attempt failed. Attempt {}".format( - self.jtag_cable_id, c + 1 - ) - ) - time.sleep(1) + # jtag_connected = False + # for c in range(self.jtag_connect_retries): + # cmd = "connect; after 1000; " + self.target_set_str( + # self.jtag_cpu_target_name + # ) + # jtag_connected = self.run_xsdb(cmd) + # if jtag_connected: + # log.info( + # "JTAG {} connection attempt successful".format(self.jtag_cable_id) + # ) + # break + # log.warning( + # "JTAG {} connection attempt failed. Attempt {}".format( + # self.jtag_cable_id, c + 1 + # ) + # ) + # time.sleep(1) + + jtag_connected = True if not jtag_connected: raise Exception( @@ -74,6 +77,66 @@ def _shell_out2(self, script): # logging.info(output.decode("utf-8")) # return output.decode("utf-8") + def run_command_realtime(self, command, shell=True): + """ + Run a shell command and print output in real-time as it's generated. + + Args: + command (str or list): The command to run. Can be a string (if shell=True) + or a list of arguments (if shell=False) + shell (bool): Whether to run the command through the shell + + Returns: + int: The return code of the command + """ + if isinstance(command, str) and not shell: + raise ValueError("If command is a string, shell must be True") + if isinstance(command, list) and shell: + raise ValueError("If command is a list, shell must be False") + + # command = "bash -c '{}'".format(command) if shell else command + try: + # log.info(f"Running command: {command.replace(';', ';\n')}") + # Start the process + process = subprocess.Popen( + command, + shell=shell, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, # Combine stderr with stdout + universal_newlines=True, # Handle text mode + bufsize=1, # Line buffered + executable="/bin/bash" # Use bash shell + ) + + # Read and print output line by line as it comes + log.info("Reading lines...") + while True: + output = process.stdout.readline() + + # If output is empty and process has finished, break + if output == '' and process.poll() is not None: + log.info("Process finished.") + break + + # Print the line if it's not empty + if output: + print(output.strip()) + sys.stdout.flush() # Force immediate output + log.info("Done...") + + # Wait for the process to complete and get return code + return_code = process.wait() + + return return_code + + except KeyboardInterrupt: + print("\nProcess interrupted by user") + process.terminate() + return -1 + except Exception as e: + print(f"Error running command: {e}") + return -1 + def run_xsdb(self, cmd): if not self.custom_vivado_path: vivado = ( @@ -88,7 +151,8 @@ def run_xsdb(self, cmd): cmd = vivado + '; xsdb -eval "{}"'.format(cmd) # cmd = [vivado + '; xsdb',' -eval "{}"'.format(cmd)] - return self._shell_out2(cmd) + # return self._shell_out2(cmd) + return self.run_command_realtime(cmd, shell=True) == 0 def restart_board(self): cmd = "connect; " @@ -221,3 +285,34 @@ 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 += f"target 1;" + cmd += "puts {Loading Bitstream}; " + cmd += f"fpga -f {bitstream}; " + cmd += "after 3000; " + cmd += "puts {Loading Stripped ELF}; " + cmd += self.target_set_str("MicroBlaze*#0") + cmd += "targets; " + cmd += "after 3000; " + cmd += f"dow {strip}; " + cmd += "con; " + cmd += "after 3000; " + + # self.run_command_realtime(cmd, shell=True) + self.run_xsdb(cmd) + # 210308A3BB8D \ No newline at end of file diff --git a/nebula/uart.py b/nebula/uart.py index 6b0f1d0c..b5cf139a 100644 --- a/nebula/uart.py +++ b/nebula/uart.py @@ -459,6 +459,61 @@ def get_local_mac_usbdev(self): """Read MAC Address of enumerated NIC on host from DUT (Pluto/M2K only)""" 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}") + + def get_ip_address_microblaze(self): + """Read IP address of DUT using ifconfig from UART for MicroBlaze""" + # cmd = "ifconfig eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127" + 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""" From e61646d3630508c7536115742f15c8ea0ee10b23 Mon Sep 17 00:00:00 2001 From: Macy Libed Date: Thu, 13 Nov 2025 16:33:24 +0800 Subject: [PATCH 10/28] Added board target name Signed-off-by: Macy Libed --- nebula/resources/template_micro_gen.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nebula/resources/template_micro_gen.yaml b/nebula/resources/template_micro_gen.yaml index 65b58be1..e6c48c96 100644 --- a/nebula/resources/template_micro_gen.yaml +++ b/nebula/resources/template_micro_gen.yaml @@ -213,6 +213,12 @@ jtag-config: optional: True 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 *" From dcace538842f17ea1d76c68a4fd5311351400740 Mon Sep 17 00:00:00 2001 From: Ace Alexander Ilog Date: Wed, 19 Nov 2025 08:15:53 +0800 Subject: [PATCH 11/28] enable microblaze target with jtag and uart interface --- nebula/jtag.py | 173 ++++++++++++++-------------- nebula/uart.py | 43 ++++++- tests/microblaze_test.py | 35 ++++++ tests/nebula_config/microblaze.yaml | 7 +- 4 files changed, 171 insertions(+), 87 deletions(-) create mode 100644 tests/microblaze_test.py diff --git a/nebula/jtag.py b/nebula/jtag.py index 79bdd814..8f481c53 100644 --- a/nebula/jtag.py +++ b/nebula/jtag.py @@ -22,37 +22,39 @@ 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 self.update_defaults_from_yaml( yamlfilename, __class__.__name__, board_name=board_name ) - + # Check target device available - # jtag_connected = False - # for c in range(self.jtag_connect_retries): - # cmd = "connect; after 1000; " + self.target_set_str( - # self.jtag_cpu_target_name - # ) - # jtag_connected = self.run_xsdb(cmd) - # if jtag_connected: - # log.info( - # "JTAG {} connection attempt successful".format(self.jtag_cable_id) - # ) - # break - # log.warning( - # "JTAG {} connection attempt failed. Attempt {}".format( - # self.jtag_cable_id, c + 1 - # ) - # ) - # time.sleep(1) - - jtag_connected = True + jtag_connected = False + for c in range(self.jtag_connect_retries): + cmd = "connect; after 1000; " + self.target_set_str( + self.jtag_cpu_target_name + ) + jtag_connected = self.run_xsdb(cmd) + if jtag_connected: + log.info( + "JTAG {} connection attempt successful".format(self.jtag_cable_id) + ) + break + log.warning( + "JTAG {} connection attempt failed. Attempt {}".format( + self.jtag_cable_id, c + 1 + ) + ) + time.sleep(1) + + #jtag_connected=True if not jtag_connected: raise Exception( @@ -77,65 +79,65 @@ def _shell_out2(self, script): # logging.info(output.decode("utf-8")) # return output.decode("utf-8") - def run_command_realtime(self, command, shell=True): - """ - Run a shell command and print output in real-time as it's generated. - - Args: - command (str or list): The command to run. Can be a string (if shell=True) - or a list of arguments (if shell=False) - shell (bool): Whether to run the command through the shell - - Returns: - int: The return code of the command - """ - if isinstance(command, str) and not shell: - raise ValueError("If command is a string, shell must be True") - if isinstance(command, list) and shell: - raise ValueError("If command is a list, shell must be False") - - # command = "bash -c '{}'".format(command) if shell else command - try: - # log.info(f"Running command: {command.replace(';', ';\n')}") - # Start the process - process = subprocess.Popen( - command, - shell=shell, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, # Combine stderr with stdout - universal_newlines=True, # Handle text mode - bufsize=1, # Line buffered - executable="/bin/bash" # Use bash shell - ) + #def run_command_realtime(self, command, shell=True): + # """ + # Run a shell command and print output in real-time as it's generated. + # + # Args: + # command (str or list): The command to run. Can be a string (if shell=True) + # or a list of arguments (if shell=False) + # shell (bool): Whether to run the command through the shell + # + # Returns: + # int: The return code of the command + # """ + # if isinstance(command, str) and not shell: + # raise ValueError("If command is a string, shell must be True") + # if isinstance(command, list) and shell: + # raise ValueError("If command is a list, shell must be False") + # + # # command = "bash -c '{}'".format(command) if shell else command + # try: + # # log.info(f"Running command: {command.replace(';', ';\n')}") + # # Start the process + # process = subprocess.Popen( + # command, + # shell=shell, + # stdout=subprocess.PIPE, + # stderr=subprocess.STDOUT, # Combine stderr with stdout + # universal_newlines=True, # Handle text mode + # bufsize=1, # Line buffered + # executable="/bin/bash" # Use bash shell + # ) + # + # # Read and print output line by line as it comes + # log.info("Reading lines...") + # while True: + # output = process.stdout.readline() + # + # # If output is empty and process has finished, break + # if output == '' and process.poll() is not None: + # log.info("Process finished.") + # break + # + # # Print the line if it's not empty + # if output: + # print(output.strip()) + # sys.stdout.flush() # Force immediate output + # log.info("Done...") + # + # # Wait for the process to complete and get return code + # return_code = process.wait() + # + # return return_code - # Read and print output line by line as it comes - log.info("Reading lines...") - while True: - output = process.stdout.readline() - - # If output is empty and process has finished, break - if output == '' and process.poll() is not None: - log.info("Process finished.") - break - - # Print the line if it's not empty - if output: - print(output.strip()) - sys.stdout.flush() # Force immediate output - log.info("Done...") - - # Wait for the process to complete and get return code - return_code = process.wait() - - return return_code - - except KeyboardInterrupt: - print("\nProcess interrupted by user") - process.terminate() - return -1 - except Exception as e: - print(f"Error running command: {e}") - return -1 + # except KeyboardInterrupt: + # print("\nProcess interrupted by user") + # process.terminate() + # return -1 + # except Exception as e: + # print(f"Error running command: {e}") + # return -1 def run_xsdb(self, cmd): if not self.custom_vivado_path: @@ -144,6 +146,7 @@ def run_xsdb(self, cmd): ) else: vivado = os.path.join(self.custom_vivado_path, "settings64.sh") + vivado = f'. {vivado}' if not os.path.isfile(vivado[2:]): raise Exception( "Vivado not found at: " + vivado[: -(len("settings64.sh") + 1)] @@ -151,8 +154,8 @@ def run_xsdb(self, cmd): cmd = vivado + '; xsdb -eval "{}"'.format(cmd) # cmd = [vivado + '; xsdb',' -eval "{}"'.format(cmd)] - # return self._shell_out2(cmd) - return self.run_command_realtime(cmd, shell=True) == 0 + return self._shell_out2(cmd) + #return self.run_command_realtime(cmd, shell=True) == 0 def restart_board(self): cmd = "connect; " @@ -172,9 +175,9 @@ def tcl_errors_recover(self): def target_set_str(self, target_name): return ( - "targets -set -filter {jtag_cable_name =~ {*" - + self.jtag_cable_id - + "} && name =~ {" + "targets -set -filter {jtag_cable_name =~ {*" + + self.jtag_cable_id + + "} && name =~ {" + target_name + "}} ; " ) @@ -301,12 +304,12 @@ def microblaze_boot_linux(self, bitstream, strip): cmd = "connect; " cmd += "after 3000; " - cmd += f"target 1;" + 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("MicroBlaze*#0") + cmd += self.target_set_str(self.jtag_cpu_target_name) cmd += "targets; " cmd += "after 3000; " cmd += f"dow {strip}; " diff --git a/nebula/uart.py b/nebula/uart.py index b5cf139a..1b9634ba 100644 --- a/nebula/uart.py +++ b/nebula/uart.py @@ -72,7 +72,7 @@ def __init__( self.dhcp = dhcp self.max_read_time = period self.fds_to_skip = ["Digilent"] - self.uboot_done_string = ["zynq-uboot>", "Zynq>", "ZynqMP>"] + self.uboot_done_string = ["zynq-uboot>", "Zynq>", "ZynqMP>", "MicroBlaze-uboot>"] self.update_defaults_from_yaml( yamlfilename, __class__.__name__, board_name=board_name ) @@ -469,6 +469,7 @@ def request_ip_dhcp_microblaze(self, nic="eth0"): self._write_data(f"udhcpc {nic}; sleep 5") address = self.get_ip_address_microblaze() log.debug(f"Got IP address: {address}") + return address def get_ip_address_microblaze(self): """Read IP address of DUT using ifconfig from UART for MicroBlaze""" @@ -665,6 +666,20 @@ def _wait_for_boot_complete_linaro(self, done_string="Welcome to Linaro 14.04"): if restart: self.start_log(logappend=True) return out + + def _wait_for_boot_complete_microblaze(self,done_string="Welcome to Buildroot"): + """Wait for Microblaze to boot by waiting for Welcome Message""" + restart = False + if self.listen_thread_run: + restart = True + self.stop_log() + out = self._read_until_done(done_string=done_string, max_time=30) + login_success = False + if out: + login_success = self._check_for_login() + if restart: + self.start_log(logappend=True) + return out and login_success def _enter_uboot_menu_from_power_cycle(self): log.info("Spamming ENTER to get UART console") @@ -689,6 +704,32 @@ def _enter_uboot_menu_from_power_cycle(self): if restart: self.start_log(logappend=True) return False + + def _enter_microblaze_prompt_from_power_cycle(self, prompt="#", max_retry=30): + log.info("Spamming Enter to get UART console") + log.info("Finding {} for max retry {}".format(prompt, max_retry)) + + if self.listen_thread_run: + restart = True + self.stop_log() + else: + restart = False + for _ in range(max_retry): + self._write_data("\r\n") + data = self._read_for_time(2) + log.info("Found data {}".format(data)) + if self._check_for_string_console(data, prompt): + log.info("Microblaze prompt reached") + if restart: + self.start_log(logappend=True) + return True + time.sleep(0.1) + log.info("Microblaze prompt not reached") + if restart: + self.start_log(logappend=True) + return False + + def _enter_linux_prompt_from_power_cycle(self, prompt="root@analog", max_retry=30): log.info("Spamming ENTER to get UART console") diff --git a/tests/microblaze_test.py b/tests/microblaze_test.py new file mode 100644 index 00000000..d26ec698 --- /dev/null +++ b/tests/microblaze_test.py @@ -0,0 +1,35 @@ +"""Test MicroBlaze target with JTAG and UART interface""" + +import os +import time +import pytest + +here = os.path.dirname(os.path.abspath(__file__)) +cfg = os.path.join(here, "nebula-microblaze.yaml") +ref_boot_folder = os.path.join(here, "microblaze_boot_files") + +log_folder = os.path.join(here, "logs") +if not os.path.exists(log_folder): + os.makedirs(log_folder) + +def test_microblaze_boot(): + + # Check boot files exist + bitstream = os.path.join(ref_boot_folder, "system_vcu118.bit") + strip = os.path.join(ref_boot_folder, "vcu118.strip") + assert os.path.isfile(bitstream), "Bitstream file not found" + assert os.path.isfile(strip), "Strip file not found" + + import nebula + + manager = nebula.manager(yamlfilename=cfg, board_name="vcu118") + # Start UART logging + manager.monitor[0].log_filename = os.path.join(log_folder, "vcu118_uart.log") + manager.monitor[0]._read_until_stop() # Flush + manager.monitor[0].start_log(logappend=True) + manager.monitor[0].print_to_console = True + # manager.monitor[0]._attemp_login("root", "root") + manager.monitor[0]._read_until_stop() # Flush + + # Boot the board + manager.jtag.microblaze_boot_linux(bitstream, strip) \ No newline at end of file diff --git a/tests/nebula_config/microblaze.yaml b/tests/nebula_config/microblaze.yaml index 9276b535..23b25e57 100644 --- a/tests/nebula_config/microblaze.yaml +++ b/tests/nebula_config/microblaze.yaml @@ -17,8 +17,11 @@ kcu105_adrv9371x: - 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: - fpga_bitstream: system_top.bit - elf_image: simpleImage.strip @@ -38,7 +41,7 @@ kcu105_adrv9371x: 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 + - logfilename: kcu105_adrv9371x.log vc707_fmcomms2-3: board-config: - board-name: vc707_fmcomms2-3 @@ -55,8 +58,10 @@ vc707_fmcomms2-3: - cf-ad9361-dds-core-lpc - cf-ad9361-lpc jtag-config: + - vivado_version: 2021.2 - jtag_cable_id: 210203A25426A - jtag_cpu_target_name: MicroBlaze*#0 + - jtag_connect_retries: "3" microblaze-config: - fpga_bitstream: system_top.bit - elf_image: simpleImage.strip From 4fc0158ecd79077f52777edf94c4f9e11887d830 Mon Sep 17 00:00:00 2001 From: Ace Alexander Ilog Date: Wed, 19 Nov 2025 11:11:30 +0800 Subject: [PATCH 12/28] microblaze downloader fixes and added a microblaze test for jtag and uart connection testing --- nebula/downloader.py | 11 +++++++---- tests/microblaze_test.py | 41 +++++++++++++++++++++++++++------------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/nebula/downloader.py b/nebula/downloader.py index 4dc6c7cc..f65ce14f 100644 --- a/nebula/downloader.py +++ b/nebula/downloader.py @@ -160,6 +160,12 @@ def gen_url(ip, branch, folder, filename, addl, url_template, source="artifactor if source == "cloudsmith": return None elif source == "artifactory": + 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, "", "") @@ -195,11 +201,7 @@ def gen_url(ip, branch, folder, filename, addl, url_template, source="artifactor release_folder = branch.upper() url = url_template.format(ip, release_folder, "", "") # folder = BUILD_DATE/PROJECT_FOLDER - folder = ( - get_newest_folder(listFD(url[:-1])) + "/boot_partition/" + str(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}") @@ -776,6 +778,7 @@ def _get_files_boot_partition( if microblaze: design_source_root = f"microblaze_images/{design_name}" print(f"DEBUG: MicroBlaze design_source_root: {design_source_root}") + print # get simpleImage log.info("Getting simpleimage") diff --git a/tests/microblaze_test.py b/tests/microblaze_test.py index d26ec698..0e16cc1f 100644 --- a/tests/microblaze_test.py +++ b/tests/microblaze_test.py @@ -1,34 +1,49 @@ -"""Test MicroBlaze target with JTAG and UART interface""" - import os import time import pytest +import shutil +from nebula import downloader here = os.path.dirname(os.path.abspath(__file__)) -cfg = os.path.join(here, "nebula-microblaze.yaml") -ref_boot_folder = os.path.join(here, "microblaze_boot_files") - +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) -def test_microblaze_boot(): +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(out_folder, "system_top.bit")) + assert os.path.isfile(os.path.join(out_folder, "simpleImage.strip")) + assert os.path.isfile(os.path.join(out_folder, "properties.yaml")) + assert os.path.isfile(os.path.join(out_folder, "hashes.txt")) - # Check boot files exist - bitstream = os.path.join(ref_boot_folder, "system_vcu118.bit") - strip = os.path.join(ref_boot_folder, "vcu118.strip") +def test_microblaze_boot(): + # Use files from outs folder + bitstream = os.path.join(out_folder, "system_top.bit") + strip = os.path.join(out_folder, "simpleImage.strip") assert os.path.isfile(bitstream), "Bitstream file not found" assert os.path.isfile(strip), "Strip file not found" import nebula - manager = nebula.manager(yamlfilename=cfg, board_name="vcu118") - # Start UART logging - manager.monitor[0].log_filename = os.path.join(log_folder, "vcu118_uart.log") + manager = nebula.manager(configfilename=cfg, board_name="kcu105_adrv9371x") + manager.monitor[0].log_filename = os.path.join(log_folder, "kcu105_adrv9371x.log") manager.monitor[0]._read_until_stop() # Flush manager.monitor[0].start_log(logappend=True) manager.monitor[0].print_to_console = True - # manager.monitor[0]._attemp_login("root", "root") manager.monitor[0]._read_until_stop() # Flush # Boot the board From 5fc71a232b16d7ecf61a159b6052864f9c98c1a5 Mon Sep 17 00:00:00 2001 From: Macy Libed Date: Thu, 20 Nov 2025 08:51:40 +0800 Subject: [PATCH 13/28] added parameter Signed-off-by: Macy Libed --- nebula/resources/template_micro_gen.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nebula/resources/template_micro_gen.yaml b/nebula/resources/template_micro_gen.yaml index e6c48c96..49c32f57 100644 --- a/nebula/resources/template_micro_gen.yaml +++ b/nebula/resources/template_micro_gen.yaml @@ -198,7 +198,7 @@ jtag-config: name: vivado_version default: 2021.1 help: "Version of vivado to use" - optional: True + optional: False netbox_field: devices.config_context.vivado_version field_2: name: custom_vivado_path @@ -210,7 +210,7 @@ jtag-config: 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: True + optional: False netbox_field: devices.console_ports.JTAG.label field_4: name: jtag_board_target_name @@ -224,6 +224,11 @@ jtag-config: 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 From 8d7f15e57b7fd709816d26a8042a658ec7bcfae8 Mon Sep 17 00:00:00 2001 From: Ace Alexander Ilog Date: Thu, 20 Nov 2025 09:34:20 +0800 Subject: [PATCH 14/28] fixed vivado path and refactored jtag and uart testing --- nebula/jtag.py | 2 +- tests/microblaze_test.py | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/nebula/jtag.py b/nebula/jtag.py index 8f481c53..44b29e26 100644 --- a/nebula/jtag.py +++ b/nebula/jtag.py @@ -146,7 +146,7 @@ def run_xsdb(self, cmd): ) else: vivado = os.path.join(self.custom_vivado_path, "settings64.sh") - vivado = f'. {vivado}' + vivado = f'. {vivado}' if not os.path.isfile(vivado[2:]): raise Exception( "Vivado not found at: " + vivado[: -(len("settings64.sh") + 1)] diff --git a/tests/microblaze_test.py b/tests/microblaze_test.py index 0e16cc1f..a5c9889c 100644 --- a/tests/microblaze_test.py +++ b/tests/microblaze_test.py @@ -16,9 +16,9 @@ def test_microblaze_downloader(): if os.path.isdir(out_folder): shutil.rmtree(out_folder) - d = downloader(yamlfilename=cfg, board_name="kcu105_adrv9371x") + d = downloader(yamlfilename=cfg, board_name="kc705_ad9467_fmc") d.download_boot_files( - "kcu105_adrv9371x", + "kc705_ad9467_fmc", source="artifactory", source_root="artifactory.analog.com", branch="main", @@ -39,12 +39,17 @@ def test_microblaze_boot(): import nebula - manager = nebula.manager(configfilename=cfg, board_name="kcu105_adrv9371x") - manager.monitor[0].log_filename = os.path.join(log_folder, "kcu105_adrv9371x.log") + manager = nebula.manager(configfilename=cfg, board_name="kc705_ad9467_fmc") + manager.monitor[0].logfilename = os.path.join(log_folder, "kc705_ad9467_fmc.log") manager.monitor[0]._read_until_stop() # Flush - manager.monitor[0].start_log(logappend=True) manager.monitor[0].print_to_console = True + manager.monitor[0].start_log(logappend=True) manager.monitor[0]._read_until_stop() # Flush # Boot the board - manager.jtag.microblaze_boot_linux(bitstream, strip) \ No newline at end of file + manager.jtag.microblaze_boot_linux(bitstream, strip) + time.sleep(60) + manager.monitor[0]._check_for_login() + time.sleep(5) + manager.monitor[0].request_ip_dhcp_microblaze() + manager.monitor[0].stop_log() \ No newline at end of file From 3cbba592d3773fdfc066a4efbf0335e6fbcb4acf Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Fri, 21 Nov 2025 15:06:38 +0800 Subject: [PATCH 15/28] updated uart communication support for MicroBlaze boards and created test for boot process --- nebula/manager.py | 1 + nebula/uart.py | 86 ++++++++++++--- tests/microblaze_test.py | 13 ++- tests/nebula_config/microblaze.yaml | 165 +++++++++++++++++----------- 4 files changed, 178 insertions(+), 87 deletions(-) diff --git a/nebula/manager.py b/nebula/manager.py index c6ea5fae..5d7d53f4 100644 --- a/nebula/manager.py +++ b/nebula/manager.py @@ -965,3 +965,4 @@ def verify_checksum(self, folder): self.net.verify_checksum( file_path=os.path.join("/boot", fname), reference=hash ) + diff --git a/nebula/uart.py b/nebula/uart.py index 1b9634ba..20bee4f3 100644 --- a/nebula/uart.py +++ b/nebula/uart.py @@ -72,7 +72,7 @@ def __init__( self.dhcp = dhcp self.max_read_time = period self.fds_to_skip = ["Digilent"] - self.uboot_done_string = ["zynq-uboot>", "Zynq>", "ZynqMP>", "MicroBlaze-uboot>"] + self.uboot_done_string = ["zynq-uboot>", "Zynq>", "ZynqMP>"] self.update_defaults_from_yaml( yamlfilename, __class__.__name__, board_name=board_name ) @@ -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) @@ -469,7 +470,7 @@ def request_ip_dhcp_microblaze(self, nic="eth0"): self._write_data(f"udhcpc {nic}; sleep 5") address = self.get_ip_address_microblaze() log.debug(f"Got IP address: {address}") - return address + return None def get_ip_address_microblaze(self): """Read IP address of DUT using ifconfig from UART for MicroBlaze""" @@ -486,7 +487,7 @@ def get_ip_address_microblaze(self): self._write_data(cmd) data = self._read_for_time(period=2) log.info("Data read: " + str(data)) - if restart: + if restart: self.start_log(logappend=True) for d in data: if isinstance(d, list): @@ -616,6 +617,7 @@ def _read_until_done(self, done_string="done", max_time=None): mt = max_time or self.max_read_time for _ in range(mt): data = self._read_until_stop() + log.info("Read data: " + str(data)) if isinstance(data, list): for d in data: for done_string in done_string_list: @@ -667,20 +669,70 @@ 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,done_string="Welcome to Buildroot"): - """Wait for Microblaze to boot by waiting for Welcome Message""" - restart = False - if self.listen_thread_run: - restart = True - self.stop_log() - out = self._read_until_done(done_string=done_string, max_time=30) - login_success = False - if out: - login_success = self._check_for_login() - if restart: - self.start_log(logappend=True) - return out and login_success + def _wait_for_boot_complete_microblaze(self, max_time=10, prompt="#"): + """Wait for MicroBlaze to boot by waiting for prompt, spamming ENTER if needed.""" + + # Flush UART buffer before starting + self._read_until_stop() + + # 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 l in lines: + if prompt in l: + 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 @@ -760,7 +812,7 @@ def load_system_uart_from_tftp(self): """Load complete system (bitstream, devtree, kernel) during uboot from TFTP""" restart = False - if self.listen_thread_run: + if self.listen_thread_run: restart = True self.stop_log() diff --git a/tests/microblaze_test.py b/tests/microblaze_test.py index a5c9889c..69afb51f 100644 --- a/tests/microblaze_test.py +++ b/tests/microblaze_test.py @@ -32,24 +32,25 @@ def test_microblaze_downloader(): def test_microblaze_boot(): # Use files from outs folder - bitstream = os.path.join(out_folder, "system_top.bit") - strip = os.path.join(out_folder, "simpleImage.strip") + 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" import nebula manager = nebula.manager(configfilename=cfg, board_name="kc705_ad9467_fmc") - manager.monitor[0].logfilename = os.path.join(log_folder, "kc705_ad9467_fmc.log") - manager.monitor[0]._read_until_stop() # Flush + manager.monitor[0].logfilename = os.path.join(log_folder, "kc705_ad9467_fmc.log") # Flush manager.monitor[0].print_to_console = True manager.monitor[0].start_log(logappend=True) - manager.monitor[0]._read_until_stop() # Flush + # Boot the board manager.jtag.microblaze_boot_linux(bitstream, strip) time.sleep(60) - manager.monitor[0]._check_for_login() + manager.monitor[0].stop_log() + manager.monitor[0]._wait_for_boot_complete_microblaze() time.sleep(5) + manager.monitor[0].stop_log() manager.monitor[0].request_ip_dhcp_microblaze() manager.monitor[0].stop_log() \ No newline at end of file diff --git a/tests/nebula_config/microblaze.yaml b/tests/nebula_config/microblaze.yaml index 23b25e57..0b28914b 100644 --- a/tests/nebula_config/microblaze.yaml +++ b/tests/nebula_config/microblaze.yaml @@ -1,84 +1,121 @@ +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: + - 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.2 + 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 + - board-name: kcu105_adrv9371x + - carrier: KCU105 + - daughter: ADRV9371 + - monitoring-interface: uart + - allow-jtag: True downloader-config: - - reference_boot_folder: kcu105_adrv9371x - - platform: Xilinx + - 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 + - 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 + - jtag_cable_id: 210308AE6A92 + - jtag_board_target_name: xcku040 + - jtag_cpu_target_name: MicroBlaze*#0 microblaze-config: - - fpga_bitstream: system_top.bit - - elf_image: simpleImage.strip + - 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 + - netbox_server: primary.englab + - netbox_server_port: '8000' + - netbox_base_url: netbox + - netbox_api_token: 4d6a33918d555e6d1e5539b9e3329d3ccb1bc6a5 network-config: - - dutip: 192.168.10.2 + - dutip: 192.168.10.2 pdu-config: - - pdu_type: cyberpower - - pduip: 192.168.10.25 - - outlet: "3" - - username: cyber - - password: cyber1 + - 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.log + - 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 + - board-name: vc707_fmcomms2-3 + - carrier: VC707 + - daughter: FMCOMMS3 + - monitoring-interface: uart + - allow-jtag: True downloader-config: - - reference_boot_folder: vc707_ad6676evb - - platform: Xilinx + - reference_boot_folder: vc707_ad6676evb + - platform: Xilinx driver-config: - - iio_device_names: - - ad9361-phy - - cf-ad9361-dds-core-lpc - - cf-ad9361-lpc + - iio_device_names: + - ad9361-phy + - cf-ad9361-dds-core-lpc + - cf-ad9361-lpc jtag-config: - - vivado_version: 2021.2 - - jtag_cable_id: 210203A25426A - - jtag_cpu_target_name: MicroBlaze*#0 - - jtag_connect_retries: "3" + - jtag_cable_id: 210203A25426A + - jtag_board_target_name: xc7vx485t + - jtag_cpu_target_name: MicroBlaze*#0 microblaze-config: - - fpga_bitstream: system_top.bit - - elf_image: simpleImage.strip + - 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 + - netbox_server: primary.englab + - netbox_server_port: '8000' + - netbox_base_url: netbox + - netbox_api_token: 4d6a33918d555e6d1e5539b9e3329d3ccb1bc6a5 network-config: - - dutip: 192.168.10.2 + - dutip: 192.168.10.2 pdu-config: - - pdu_type: cyberpower - - pduip: 192.168.10.24 - - outlet: "6" - - username: cyber - - password: cyber + - 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 + - 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 From aff966096acf0f4896144fb0b034e42f8ba3eadc Mon Sep 17 00:00:00 2001 From: Macy Libed Date: Tue, 25 Nov 2025 09:17:48 +0800 Subject: [PATCH 16/28] Microblaze graph WIP Signed-off-by: Macy Libed --- doc/microblaze_boot_test_flow.pu | 44 +++++++++++++++++++++++++++++++ doc/microblaze_downloader_flow.pu | 17 ++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 doc/microblaze_boot_test_flow.pu create mode 100644 doc/microblaze_downloader_flow.pu 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 From e34cb3f2718ff46df3baeda6fefd6ea0aaaff9f8 Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Wed, 26 Nov 2025 09:53:08 +0800 Subject: [PATCH 17/28] added a microblaze flag for update bootfiles cli interface --- nebula/manager.py | 79 +++++++++++--- nebula/tasks.py | 4 +- tests/microblaze_test.py | 17 ++- tests/nebula_config/microblaze.yaml | 160 +++++++--------------------- 4 files changed, 113 insertions(+), 147 deletions(-) diff --git a/nebula/manager.py b/nebula/manager.py index 5d7d53f4..28051379 100644 --- a/nebula/manager.py +++ b/nebula/manager.py @@ -434,6 +434,33 @@ def board_reboot_jtag_uart( self.network_check() self.monitor[0].stop_log() + @_release_thread_lock + 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 + self.monitor[0].start_log(logappend=True) + + 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(30) + self.monitor[0].stop_log() + self.monitor[0]._wait_for_boot_complete_microblaze() + + 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() + self.monitor[0].stop_log() + @_release_thread_lock # type: ignore def board_reboot_uart_net_pdu( self, @@ -747,7 +774,7 @@ 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) @@ -772,6 +799,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"], @@ -784,7 +812,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]: @@ -796,20 +828,25 @@ 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 @@ -835,6 +872,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/tasks.py b/nebula/tasks.py index 854555a3..db2596ba 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,6 +913,7 @@ 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) @@ -925,7 +927,7 @@ 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( diff --git a/tests/microblaze_test.py b/tests/microblaze_test.py index 69afb51f..83c8f177 100644 --- a/tests/microblaze_test.py +++ b/tests/microblaze_test.py @@ -41,16 +41,11 @@ def test_microblaze_boot(): manager = nebula.manager(configfilename=cfg, board_name="kc705_ad9467_fmc") manager.monitor[0].logfilename = os.path.join(log_folder, "kc705_ad9467_fmc.log") # Flush - manager.monitor[0].print_to_console = True - manager.monitor[0].start_log(logappend=True) - + manager.board_reboot_auto_folder( + out_folder, + microblaze=True + ) # Boot the board - manager.jtag.microblaze_boot_linux(bitstream, strip) - time.sleep(60) - manager.monitor[0].stop_log() - manager.monitor[0]._wait_for_boot_complete_microblaze() - time.sleep(5) - manager.monitor[0].stop_log() - manager.monitor[0].request_ip_dhcp_microblaze() - manager.monitor[0].stop_log() \ No newline at end of file + ip = manager.monitor[0].get_ip_address_microblaze() + assert ip is not None, "MicroBlaze board did not obtain an IP address" \ No newline at end of file diff --git a/tests/nebula_config/microblaze.yaml b/tests/nebula_config/microblaze.yaml index 0b28914b..49ef14b4 100644 --- a/tests/nebula_config/microblaze.yaml +++ b/tests/nebula_config/microblaze.yaml @@ -1,121 +1,39 @@ -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: - - 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.2 - 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: - - jtag_cable_id: 210308AE6A92 - - jtag_board_target_name: xcku040 - - jtag_cpu_target_name: MicroBlaze*#0 - microblaze-config: - - 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.2 - 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: - - jtag_cable_id: 210203A25426A - - jtag_board_target_name: xc7vx485t - - jtag_cpu_target_name: MicroBlaze*#0 - microblaze-config: - - 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.2 - 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 +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: +- 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 From cb54508d77ab46a9b8c7eae5aca141a130ab2513 Mon Sep 17 00:00:00 2001 From: "Travis F. Collins" Date: Tue, 4 Nov 2025 10:30:23 -0500 Subject: [PATCH 18/28] enabled microblaze boards in cli interface and added a monkey patch workaround for drop bear bugs --- nebula/manager.py | 25 ++++--- nebula/network.py | 171 ++++++++++++++++++++++++++++++++++++++++------ nebula/tasks.py | 2 +- nebula/uart.py | 4 -- 4 files changed, 167 insertions(+), 35 deletions(-) diff --git a/nebula/manager.py b/nebula/manager.py index 28051379..85240c55 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 @@ -104,7 +105,7 @@ def __init__( # noqa:C901 configfilename = None else: configfilename = self.configfilename - self.net = network(yamlfilename=configfilename, board_name=board_name) + self.net = network(yamlfilename=configfilename, board_name=board_name, microblaze=microblaze) log.info("Networking initialized") self.reference_boot_folder = None @@ -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 @@ -444,21 +452,22 @@ def board_boot_microblaze_jtag_uart( over JTAG. Then over UART boot """ self.monitor[0]._read_until_stop() # Flush - self.monitor[0].start_log(logappend=True) + 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(30) + 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() + self.network_check(microblaze = True) self.monitor[0].stop_log() @_release_thread_lock # type: ignore diff --git a/nebula/network.py b/nebula/network.py index 5708dc4f..3151c3fb 100644 --- a/nebula/network.py +++ b/nebula/network.py @@ -10,11 +10,41 @@ import fabric from fabric import Connection +from paramiko import AutoAddPolicy +import paramiko.packet +import paramiko.common +import yaml import nebula.errors as ne import nebula.helper as helper from nebula.common import utils +# Monkey-patch Paramiko to skip MAC verification for dropbear compatibility +# This works around a known bug in older dropbear versions with large data transfers +_original_read_message = paramiko.packet.Packetizer.read_message + +def _patched_read_message(self): + """Patched read_message that skips MAC check on verification failure""" + try: + return _original_read_message(self) + except paramiko.SSHException as e: + if "Mismatched MAC" in str(e): + # Log the error but don't fail - this is a known dropbear bug + logging.getLogger(__name__).warning(f"MAC mismatch detected (dropbear bug), continuing anyway") + # Return empty message to let the connection continue + from paramiko.message import Message + return paramiko.common.MSG_IGNORE, Message() + raise + +def apply_monkey_patch_for_microblaze(microblaze): + log.info("checking if monkey patch needed for microblaze: " + str(microblaze)) + if microblaze: + log.info("Applying Paramiko monkey patch for Microblaze dropbear compatibility") + paramiko.packet.Packetizer.read_message = _patched_read_message + else: + log.info("Restoring original Paramiko read_message method") + paramiko.packet.Packetizer.read_message = _original_read_message + log = logging.getLogger(__name__) @@ -29,7 +59,11 @@ def __init__( nicip=None, yamlfilename=None, board_name=None, + microblaze=False, ): + # Apply the monkey patch conditionally + apply_monkey_patch_for_microblaze(microblaze) + props = ["dutip", "dutusername", "dutpassword", "dhcp", "nic", "nicip"] for prop in props: setattr(self, prop, None) @@ -48,6 +82,32 @@ def __init__( self.dhcp = False self.ssh_timeout = 30 self.board_name = board_name + self.pty = True # Use PTY mode to avoid MAC mismatch errors with dropbear + + def _get_connection(self): + """Create a fabric Connection with host key checking disabled""" + conn = Connection( + self.dutusername + "@" + self.dutip, + connect_kwargs={ + "password": self.dutpassword, + "allow_agent": False, + "look_for_keys": False, + "disabled_algorithms": {}, # Ensure no algorithms are disabled + "banner_timeout": 60, # Increase banner timeout for slow connections + }, + ) + # Disable SSH host key checking by setting policy to auto-add + conn.open() + if conn.client: + conn.client.set_missing_host_key_policy(AutoAddPolicy()) + # Set transport options for better compatibility with dropbear + if conn.client.get_transport(): + transport = conn.client.get_transport() + transport.window_size = 2147483647 # Maximum window size + transport.max_packet_size = 32768 # Smaller packet size for compatibility + transport.packetizer.REKEY_BYTES = pow(2, 30) # Delay rekeying + transport.packetizer.REKEY_PACKETS = pow(2, 30) + return conn def ping_board(self, tries=10): """Ping board and check if any received @@ -66,8 +126,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") @@ -82,24 +144,31 @@ def check_ssh(self): """ retries = 3 for t in range(retries): + conn = None try: log.info("Checking for board through SSH") - result = fabric.Connection( - self.dutusername + "@" + self.dutip, - connect_kwargs={"password": self.dutpassword}, - ).run( + conn = self._get_connection() + result = conn.run( "uname -a", hide=True, timeout=self.ssh_timeout, - pty=True, + pty=self.pty, # Use PTY to avoid MAC mismatch errors with dropbear in_stream=False, ) + log.info("SSH successful") break except Exception as ex: log.warning("Exception raised: " + str(ex)) time.sleep(3) if t >= (retries - 1): + log.error("SSH failed after all retries") raise Exception("SSH Failed") + finally: + if conn is not None: + try: + conn.close() + except: + pass return result.failed def check_board_booted(self): @@ -122,11 +191,10 @@ def reboot_board(self, bypass_sleep=False): # Try to reboot board with SSH if possible retries = 3 for t in range(retries): + conn = None try: - result = fabric.Connection( - self.dutusername + "@" + self.dutip, - connect_kwargs={"password": self.dutpassword}, - ).run("/sbin/reboot", hide=False) + conn = self._get_connection() + result = conn.run("/sbin/reboot", hide=True, pty=self.pty) if result.ok: print("Rebooting board with SSH") if not bypass_sleep: @@ -141,6 +209,12 @@ def reboot_board(self, bypass_sleep=False): time.sleep(3) if t >= (retries - 1): raise Exception("Exception occurred during SSH Reboot", str(ex)) + finally: + if conn is not None: + try: + conn.close() + except: + pass def run_ssh_command( self, @@ -156,15 +230,14 @@ def run_ssh_command( log.info( "ssh command:" + command + " to " + self.dutusername + "@" + self.dutip ) + conn = None try: - result = fabric.Connection( - self.dutusername + "@" + self.dutip, - connect_kwargs={"password": self.dutpassword}, - ).run( + conn = self._get_connection() + result = conn.run( command, hide=True, timeout=self.ssh_timeout, - pty=True, + pty=self.pty, # Use PTY to avoid MAC mismatch errors with dropbear in_stream=False, ) if result.failed: @@ -191,6 +264,13 @@ def run_ssh_command( break except Exception as ex: log.warning("Exception raised: " + str(ex)) + finally: + # Always close the connection to prevent state issues + if conn is not None: + try: + conn.close() + except: + pass if not ignore_exceptions: time.sleep(3) if t >= (retries - 1): @@ -202,16 +282,22 @@ def copy_file_to_remote(self, src, dest): retries = 3 log.info("Copying file to remote: " + src) for t in range(retries): + conn = None try: - Connection( - self.dutusername + "@" + self.dutip, - connect_kwargs={"password": self.dutpassword}, - ).put(src, remote=dest) + conn = self._get_connection() + conn.put(src, remote=dest) + break except Exception as ex: log.warning("Exception raised: " + str(ex)) time.sleep(3) if t >= (retries - 1): raise ne.SSHError + finally: + if conn is not None: + try: + conn.close() + except: + pass def update_boot_partition( self, @@ -310,10 +396,16 @@ def update_boot_partition_existing_files(self, subfolder=None): self.run_ssh_command("sudo reboot", ignore_exceptions=True) def _dl_file(self, filename): - fabric.Connection( - self.dutusername + "@" + self.dutip, - connect_kwargs={"password": self.dutpassword}, - ).get(filename) + conn = None + try: + conn = self._get_connection() + conn.get(filename) + finally: + if conn is not None: + try: + conn.close() + except: + pass def check_dmesg(self, error_on_warnings=False): """check_dmesg: @@ -422,3 +514,38 @@ def verify_checksum(self, file_path, reference, algo="sha256"): f"Checksum does not match for {file_path}:\ Ref: {reference} Actual: {result.stdout.strip()}" ) + + def monitor_dmesg(self, find_string, max_timeout_seconds=60, enable_log=True): + """monitor_dmesg: + Monitor dmesg for specific strings + + return: + status: 0 if no errors found, 1 otherwise + """ + if not isinstance(find_string, str): + raise Exception("find_string must be a string") + log.info(f"Monitoring dmesg for string {find_string}") + start_time = time.time() + connection = self._get_connection() + with connection as c: + while True: + time.sleep(5) + dmesg_stream = c.run("dmesg", hide=True, pty=self.pty) + if not isinstance(dmesg_stream.stdout, list): + stdout = dmesg_stream.stdout.splitlines() + else: + stdout = dmesg_stream.stdout + for line in stdout: + if enable_log: + log.info(f"Got: {line}") + if find_string in line: + log.info(f"Found string {find_string} in dmesg") + return True + if (time.time() - start_time) > max_timeout_seconds: + log.info(f"Timeout waiting for string {find_string} in dmesg") + if isinstance(stdout, list): + stdout = "\n".join(stdout) + log.debug(f"Full log:\n{stdout}") + break + time.sleep(2) # wait a bit before returning to close ssh connection + return False diff --git a/nebula/tasks.py b/nebula/tasks.py index db2596ba..c1152a56 100644 --- a/nebula/tasks.py +++ b/nebula/tasks.py @@ -916,7 +916,7 @@ def update_boot_files_manager( 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( diff --git a/nebula/uart.py b/nebula/uart.py index 20bee4f3..3d69d1d3 100644 --- a/nebula/uart.py +++ b/nebula/uart.py @@ -617,7 +617,6 @@ def _read_until_done(self, done_string="done", max_time=None): mt = max_time or self.max_read_time for _ in range(mt): data = self._read_until_stop() - log.info("Read data: " + str(data)) if isinstance(data, list): for d in data: for done_string in done_string_list: @@ -673,9 +672,6 @@ def _wait_for_boot_complete_linaro(self, done_string="Welcome to Linaro 14.04"): def _wait_for_boot_complete_microblaze(self, max_time=10, prompt="#"): """Wait for MicroBlaze to boot by waiting for prompt, spamming ENTER if needed.""" - # Flush UART buffer before starting - self._read_until_stop() - # Spam ENTER until "Welcome to Buildroot" is found or timeout for _ in range(max_time): self._write_data("\n") # Spam ENTER From ce48568595ac0ee8960533fb410c3394a4605f9e Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Wed, 3 Dec 2025 08:55:00 +0800 Subject: [PATCH 19/28] skip get_sha in downloader for microblaze boards --- nebula/downloader.py | 20 ++++++++++++++------ tests/microblaze_test.py | 8 ++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/nebula/downloader.py b/nebula/downloader.py index f65ce14f..3efc93be 100644 --- a/nebula/downloader.py +++ b/nebula/downloader.py @@ -858,12 +858,20 @@ def _get_files_boot_partition( 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 diff --git a/tests/microblaze_test.py b/tests/microblaze_test.py index 83c8f177..1e6da61a 100644 --- a/tests/microblaze_test.py +++ b/tests/microblaze_test.py @@ -16,9 +16,9 @@ def test_microblaze_downloader(): if os.path.isdir(out_folder): shutil.rmtree(out_folder) - d = downloader(yamlfilename=cfg, board_name="kc705_ad9467_fmc") + d = downloader(yamlfilename=cfg, board_name="kcu105_adrv9371x") d.download_boot_files( - "kc705_ad9467_fmc", + "kcu105_adrv9371x", source="artifactory", source_root="artifactory.analog.com", branch="main", @@ -39,8 +39,8 @@ def test_microblaze_boot(): import nebula - manager = nebula.manager(configfilename=cfg, board_name="kc705_ad9467_fmc") - manager.monitor[0].logfilename = os.path.join(log_folder, "kc705_ad9467_fmc.log") # Flush + manager = nebula.manager(configfilename=cfg, board_name="kcu105_adrv9371x", microblaze=True) + manager.monitor[0].logfilename = os.path.join(log_folder, "kcu105_adrv9371x.log") # Flush manager.board_reboot_auto_folder( out_folder, From 31885b8723b58391b74cf1c174237e0ec2fbad55 Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Wed, 3 Dec 2025 15:22:54 +0800 Subject: [PATCH 20/28] changed dutip to null when generating nebula-config --- nebula/resources/template_micro_gen.yaml | 2 +- tests/nebula_config/microblaze.yaml | 28 ++++++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/nebula/resources/template_micro_gen.yaml b/nebula/resources/template_micro_gen.yaml index 49c32f57..d134de43 100644 --- a/nebula/resources/template_micro_gen.yaml +++ b/nebula/resources/template_micro_gen.yaml @@ -45,7 +45,7 @@ board-config: network-config: field_1: name: dutip - default: 192.168.10.2 + default: help: "IP address of development board" optional: False netbox_field: devices.interfaces.mgmt.ip.address diff --git a/tests/nebula_config/microblaze.yaml b/tests/nebula_config/microblaze.yaml index 49ef14b4..5f5f1e6a 100644 --- a/tests/nebula_config/microblaze.yaml +++ b/tests/nebula_config/microblaze.yaml @@ -1,20 +1,24 @@ board-config: -- board-name: kc705_ad9467_fmc -- carrier: KC705 -- daughter: AD9467 +- board-name: kcu105_adrv9371x +- carrier: KCU105 +- daughter: ADRV9371 - monitoring-interface: uart - allow-jtag: True downloader-config: -- reference_boot_folder: kc705_ad9467_fmc +- reference_boot_folder: kcu105_adrv9371x - platform: Xilinx driver-config: - iio_device_names: - - ad9517-4 - - cf-ad9467-core-lpc + - 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: 210203859085A -- jtag_board_target_name: xc7k325t +- jtag_cable_id: 210308AE6A92 +- jtag_board_target_name: xcku040 - jtag_cpu_target_name: MicroBlaze*#0 - jtag_connect_retries: 3 microblaze-config: @@ -26,14 +30,14 @@ netbox-config: - netbox_base_url: netbox - netbox_api_token: 4d6a33918d555e6d1e5539b9e3329d3ccb1bc6a5 network-config: -- dutip: 192.168.10.238 +- dutip: 192.168.10.237 pdu-config: - pdu_type: cyberpower - pduip: 192.168.10.25 -- outlet: '4' +- outlet: '3' - username: cyber - password: cyber1 uart-config: -- address: /dev/serial/by-path/pci-0000:00:14.0-usb-0:6.4:1.0-port0 +- address: /dev/serial/by-path/pci-0000:00:14.0-usb-0:6.2:1.1-port0 - baudrate: '115200' -- logfilename: kc705_ad9467_fmc_iio.log +- logfilename: kcu105_adrv9371x_iio.log From c7680ca4b0ff81774e1e6ee4d1c2b8ad94866ccf Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Wed, 3 Dec 2025 15:22:54 +0800 Subject: [PATCH 21/28] changed dutip to null when generating nebula-config, added a fix for global variable in network --- nebula/network.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/nebula/network.py b/nebula/network.py index 3151c3fb..b97cc36f 100644 --- a/nebula/network.py +++ b/nebula/network.py @@ -190,6 +190,7 @@ def reboot_board(self, bypass_sleep=False): log.info("Rebooting board over SSH") # Try to reboot board with SSH if possible retries = 3 + ex = None for t in range(retries): conn = None try: @@ -204,7 +205,8 @@ def reboot_board(self, bypass_sleep=False): # Use PDU raise Exception("PDU reset not implemented yet") - except Exception as ex: + except Exception as inner_ex: + ex = inner_ex log.warning("Exception raised: " + str(ex)) time.sleep(3) if t >= (retries - 1): @@ -226,6 +228,7 @@ def run_ssh_command( ): result = None filename = None + ex = None for t in range(retries): log.info( "ssh command:" + command + " to " + self.dutusername + "@" + self.dutip @@ -262,7 +265,8 @@ def run_ssh_command( with open(f"{self.board_name}_err_{filename}.log", "w") as f: f.write(result.stderr) break - except Exception as ex: + except Exception as inner_ex: + ex = inner_ex log.warning("Exception raised: " + str(ex)) finally: # Always close the connection to prevent state issues @@ -280,6 +284,7 @@ def run_ssh_command( def copy_file_to_remote(self, src, dest): retries = 3 + ex = None log.info("Copying file to remote: " + src) for t in range(retries): conn = None @@ -287,7 +292,8 @@ def copy_file_to_remote(self, src, dest): conn = self._get_connection() conn.put(src, remote=dest) break - except Exception as ex: + except Exception as inner_ex: + ex = inner_ex log.warning("Exception raised: " + str(ex)) time.sleep(3) if t >= (retries - 1): From 8e5dd4114aae766914cace053ccf2a6485c698a1 Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Wed, 10 Dec 2025 12:22:13 +0800 Subject: [PATCH 22/28] added a try catch method for ssh connection and dmesg commands --- nebula/jtag.py | 60 ---------- nebula/manager.py | 2 +- nebula/network.py | 107 ++++++++++------- nebula/tasks.py | 4 +- nebula/uart.py | 26 ----- tests/microblaze_test.py | 29 +++-- tests/nebula_config/microblaze.yaml | 171 +++++++++++++++++++++------- 7 files changed, 214 insertions(+), 185 deletions(-) diff --git a/nebula/jtag.py b/nebula/jtag.py index 44b29e26..53d16289 100644 --- a/nebula/jtag.py +++ b/nebula/jtag.py @@ -79,66 +79,6 @@ def _shell_out2(self, script): # logging.info(output.decode("utf-8")) # return output.decode("utf-8") - #def run_command_realtime(self, command, shell=True): - # """ - # Run a shell command and print output in real-time as it's generated. - # - # Args: - # command (str or list): The command to run. Can be a string (if shell=True) - # or a list of arguments (if shell=False) - # shell (bool): Whether to run the command through the shell - # - # Returns: - # int: The return code of the command - # """ - # if isinstance(command, str) and not shell: - # raise ValueError("If command is a string, shell must be True") - # if isinstance(command, list) and shell: - # raise ValueError("If command is a list, shell must be False") - # - # # command = "bash -c '{}'".format(command) if shell else command - # try: - # # log.info(f"Running command: {command.replace(';', ';\n')}") - # # Start the process - # process = subprocess.Popen( - # command, - # shell=shell, - # stdout=subprocess.PIPE, - # stderr=subprocess.STDOUT, # Combine stderr with stdout - # universal_newlines=True, # Handle text mode - # bufsize=1, # Line buffered - # executable="/bin/bash" # Use bash shell - # ) - # - # # Read and print output line by line as it comes - # log.info("Reading lines...") - # while True: - # output = process.stdout.readline() - # - # # If output is empty and process has finished, break - # if output == '' and process.poll() is not None: - # log.info("Process finished.") - # break - # - # # Print the line if it's not empty - # if output: - # print(output.strip()) - # sys.stdout.flush() # Force immediate output - # log.info("Done...") - # - # # Wait for the process to complete and get return code - # return_code = process.wait() - # - # return return_code - - # except KeyboardInterrupt: - # print("\nProcess interrupted by user") - # process.terminate() - # return -1 - # except Exception as e: - # print(f"Error running command: {e}") - # return -1 - def run_xsdb(self, cmd): if not self.custom_vivado_path: vivado = ( diff --git a/nebula/manager.py b/nebula/manager.py index 85240c55..94e81e59 100644 --- a/nebula/manager.py +++ b/nebula/manager.py @@ -105,7 +105,7 @@ def __init__( # noqa:C901 configfilename = None else: configfilename = self.configfilename - self.net = network(yamlfilename=configfilename, board_name=board_name, microblaze=microblaze) + self.net = network(yamlfilename=configfilename, board_name=board_name) log.info("Networking initialized") self.reference_boot_folder = None diff --git a/nebula/network.py b/nebula/network.py index b97cc36f..cd58d017 100644 --- a/nebula/network.py +++ b/nebula/network.py @@ -13,7 +13,7 @@ from paramiko import AutoAddPolicy import paramiko.packet import paramiko.common -import yaml +import logging import nebula.errors as ne import nebula.helper as helper @@ -36,14 +36,7 @@ def _patched_read_message(self): return paramiko.common.MSG_IGNORE, Message() raise -def apply_monkey_patch_for_microblaze(microblaze): - log.info("checking if monkey patch needed for microblaze: " + str(microblaze)) - if microblaze: - log.info("Applying Paramiko monkey patch for Microblaze dropbear compatibility") - paramiko.packet.Packetizer.read_message = _patched_read_message - else: - log.info("Restoring original Paramiko read_message method") - paramiko.packet.Packetizer.read_message = _original_read_message +paramiko.packet.Packetizer.read_message = _patched_read_message log = logging.getLogger(__name__) @@ -59,10 +52,7 @@ def __init__( nicip=None, yamlfilename=None, board_name=None, - microblaze=False, ): - # Apply the monkey patch conditionally - apply_monkey_patch_for_microblaze(microblaze) props = ["dutip", "dutusername", "dutpassword", "dhcp", "nic", "nicip"] for prop in props: @@ -84,6 +74,17 @@ def __init__( self.board_name = board_name self.pty = True # Use PTY mode to avoid MAC mismatch errors with dropbear + self.microblaze_enable = any( + keyword in self.board_name.lower() for keyword in ["kcu", "vc", "kc"] + ) + # Apply monkey patch for MicroBlaze boards if enabled + if self.microblaze_enable: + log.info("Applying Paramiko monkey patch for MicroBlaze dropbear compatibility") + paramiko.packet.Packetizer.read_message = _patched_read_message + else: + log.info("Using standard Paramiko read_message method") + paramiko.packet.Packetizer.read_message = _original_read_message + def _get_connection(self): """Create a fabric Connection with host key checking disabled""" conn = Connection( @@ -97,16 +98,20 @@ def _get_connection(self): }, ) # Disable SSH host key checking by setting policy to auto-add - conn.open() - if conn.client: - conn.client.set_missing_host_key_policy(AutoAddPolicy()) - # Set transport options for better compatibility with dropbear - if conn.client.get_transport(): - transport = conn.client.get_transport() - transport.window_size = 2147483647 # Maximum window size - transport.max_packet_size = 32768 # Smaller packet size for compatibility - transport.packetizer.REKEY_BYTES = pow(2, 30) # Delay rekeying - transport.packetizer.REKEY_PACKETS = pow(2, 30) + try: + conn.open() + if conn.client: + conn.client.set_missing_host_key_policy(AutoAddPolicy()) + # Set transport options for better compatibility with dropbear + if conn.client.get_transport(): + transport = conn.client.get_transport() + transport.window_size = 2147483647 # Maximum window size + transport.max_packet_size = 32768 # Smaller packet size for compatibility + transport.packetizer.REKEY_BYTES = pow(2, 30) # Delay rekeying + transport.packetizer.REKEY_PACKETS = pow(2, 30) + except Exception as e: + log.error(f"Failed to establish SSH connection: {e}") + raise return conn def ping_board(self, tries=10): @@ -152,7 +157,8 @@ def check_ssh(self): "uname -a", hide=True, timeout=self.ssh_timeout, - pty=self.pty, # Use PTY to avoid MAC mismatch errors with dropbear + pty=self.pty, + warn = True, in_stream=False, ) log.info("SSH successful") @@ -243,7 +249,9 @@ def run_ssh_command( pty=self.pty, # Use PTY to avoid MAC mismatch errors with dropbear in_stream=False, ) + if result.failed: + log.error(f"Command failed: {command}") raise Exception("Failed to run command:", command) if print_result_to_file: @@ -267,19 +275,15 @@ def run_ssh_command( break except Exception as inner_ex: ex = inner_ex - log.warning("Exception raised: " + str(ex)) + log.warning("Exception raised: " + str(ex), exc_info=True) + if not ignore_exceptions and t >= (retries - 1): + raise Exception("SSH Failed: " + str(ex)) finally: - # Always close the connection to prevent state issues if conn is not None: try: conn.close() - except: - pass - if not ignore_exceptions: - time.sleep(3) - if t >= (retries - 1): - raise Exception("SSH Failed: " + str(ex)) - + except Exception as close_ex: + log.warning("Failed to close connection: " + str(close_ex)) return result def copy_file_to_remote(self, src, dest): @@ -428,7 +432,21 @@ 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: @@ -448,18 +466,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: @@ -467,12 +488,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) @@ -484,7 +503,9 @@ 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): diff --git a/nebula/tasks.py b/nebula/tasks.py index c1152a56..906a78e3 100644 --- a/nebula/tasks.py +++ b/nebula/tasks.py @@ -762,7 +762,7 @@ def gen_config_netbox( netbox_baseurl=None, jenkins_agent=None, board_name=None, - include_variants=False, + include_variants=True, include_children=True, devices_status="active", devices_role="fpga-dut", @@ -1272,7 +1272,7 @@ 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 3d69d1d3..71ef2a26 100644 --- a/nebula/uart.py +++ b/nebula/uart.py @@ -752,32 +752,6 @@ def _enter_uboot_menu_from_power_cycle(self): if restart: self.start_log(logappend=True) return False - - def _enter_microblaze_prompt_from_power_cycle(self, prompt="#", max_retry=30): - log.info("Spamming Enter to get UART console") - log.info("Finding {} for max retry {}".format(prompt, max_retry)) - - if self.listen_thread_run: - restart = True - self.stop_log() - else: - restart = False - for _ in range(max_retry): - self._write_data("\r\n") - data = self._read_for_time(2) - log.info("Found data {}".format(data)) - if self._check_for_string_console(data, prompt): - log.info("Microblaze prompt reached") - if restart: - self.start_log(logappend=True) - return True - time.sleep(0.1) - log.info("Microblaze prompt not reached") - if restart: - self.start_log(logappend=True) - return False - - def _enter_linux_prompt_from_power_cycle(self, prompt="root@analog", max_retry=30): log.info("Spamming ENTER to get UART console") diff --git a/tests/microblaze_test.py b/tests/microblaze_test.py index 1e6da61a..a5e405d0 100644 --- a/tests/microblaze_test.py +++ b/tests/microblaze_test.py @@ -3,6 +3,9 @@ import pytest import shutil from nebula import downloader +from nebula import network +import nebula + here = os.path.dirname(os.path.abspath(__file__)) cfg = os.path.join(here, "nebula_config","microblaze.yaml") @@ -10,6 +13,7 @@ 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 @@ -25,10 +29,10 @@ def test_microblaze_downloader(): microblaze=True, ) - assert os.path.isfile(os.path.join(out_folder, "system_top.bit")) - assert os.path.isfile(os.path.join(out_folder, "simpleImage.strip")) - assert os.path.isfile(os.path.join(out_folder, "properties.yaml")) - assert os.path.isfile(os.path.join(out_folder, "hashes.txt")) + 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 @@ -37,15 +41,20 @@ def test_microblaze_boot(): assert os.path.isfile(bitstream), "Bitstream file not found" assert os.path.isfile(strip), "Strip file not found" - import nebula - manager = nebula.manager(configfilename=cfg, board_name="kcu105_adrv9371x", microblaze=True) - manager.monitor[0].logfilename = os.path.join(log_folder, "kcu105_adrv9371x.log") # Flush + 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 ) - # Boot the board - ip = manager.monitor[0].get_ip_address_microblaze() - assert ip is not None, "MicroBlaze board did not obtain an IP address" \ No newline at end of file + +#@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() + \ No newline at end of file diff --git a/tests/nebula_config/microblaze.yaml b/tests/nebula_config/microblaze.yaml index 5f5f1e6a..63c89539 100644 --- a/tests/nebula_config/microblaze.yaml +++ b/tests/nebula_config/microblaze.yaml @@ -1,43 +1,128 @@ -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: -- 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 +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 From 6093615a960bf90ff7d393b8959a552c08c02bf1 Mon Sep 17 00:00:00 2001 From: Trecia Agoylo Date: Thu, 18 Dec 2025 15:57:22 +0800 Subject: [PATCH 23/28] Fixed Lint Signed-off-by: Trecia Agoylo --- nebula/downloader.py | 127 ++++++++++++----------- nebula/jtag.py | 18 ++-- nebula/manager.py | 24 +++-- nebula/network.py | 46 +++++--- nebula/resources/template_micro_gen.yaml | 2 +- nebula/tasks.py | 18 +++- nebula/uart.py | 27 ++--- tests/microblaze_test.py | 48 +++++---- tests/test_downloader.py | 22 ++-- 9 files changed, 187 insertions(+), 145 deletions(-) diff --git a/nebula/downloader.py b/nebula/downloader.py index 3efc93be..722d9590 100644 --- a/nebula/downloader.py +++ b/nebula/downloader.py @@ -156,64 +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" and "boot_partition" in url_template and "microblaze_images" in str(folder): +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 = get_newest_folder(listFD(url[:-1])) + "/" + str(folder) - print(f"debug: microblaze folder constructed: {folder}") + # folder = BUILD_DATE/PROJECT_FOLDER + folder = ( + get_newest_folder(listFD(url[:-1])) + + "/boot_partition/" + + str(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) + 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: - 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) + release_folder = get_latest_release(listFD(url)) 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)) + 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: - 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 - 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: - folder = ( - get_newest_folder(listFD(url[:-1])) + "/boot_partition/" + str(folder) - ) + 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: folder = ( - get_newest_folder(listFD(url[:-1])) + "/boot_partition/" + str(folder) - ) - return url_template.format(ip, release_folder, folder, filename) + 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"): @@ -774,7 +771,7 @@ def _get_files_boot_partition( ) else: url_template = "https://{}/artifactory/sdg-generic-development/boot_partition/{}/{}/{}" - + if microblaze: design_source_root = f"microblaze_images/{design_name}" print(f"DEBUG: MicroBlaze design_source_root: {design_source_root}") @@ -817,7 +814,9 @@ def _get_files_boot_partition( ) if boot_subfolder is not None: - design_source_root = os.path.join(reference_boot_folder, boot_subfolder) + design_source_root = os.path.join( + reference_boot_folder, boot_subfolder + ) else: design_source_root = reference_boot_folder # Get BOOT.BIN @@ -840,11 +839,13 @@ def _get_files_boot_partition( 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 + design_source_root = ( + reference_boot_folder + "/" + devicetree_subfolder + ) else: design_source_root = reference_boot_folder self._get_file( @@ -859,8 +860,8 @@ def _get_files_boot_partition( if source == "artifactory": # check if info_txt is present if microblaze: - #skip get_gitsha for microblaze - try: + # skip get_gitsha for microblaze + try: build_info = get_info_txt(url_template) except Exception as e: log.warn(e) @@ -970,7 +971,7 @@ def _get_files_linux( else: url_template = "https://{}/artifactory/sdg-generic-development/linux/releases/{}/{}/{}" - #if microblaze: + # if microblaze: # if branch == "main": # url_template = ( # "https://{}/artifactory/sdg-generic-development/boot_partition/{}/{}/{}") @@ -998,8 +999,8 @@ def _get_files_linux( # branch, # url_template=url_template, # ) - #else: - + # else: + # Get files from linux folder # Get kernel log.info("Getting " + kernel) @@ -1206,10 +1207,10 @@ def _get_files( ) if microblaze: - #hdl_branch = "master" if branch == "main" else branch - #self._get_files_hdl( + # hdl_branch = "master" if branch == "main" else branch + # self._get_files_hdl( # hdl_folder, source, source_root, hdl_branch, hdl_output=True - #) + # ) self._get_files_boot_partition( reference_boot_folder, devicetree_subfolder, diff --git a/nebula/jtag.py b/nebula/jtag.py index 53d16289..423b6587 100644 --- a/nebula/jtag.py +++ b/nebula/jtag.py @@ -2,8 +2,8 @@ import os import shutil import subprocess -import time import sys +import time from nebula.common import utils @@ -34,7 +34,7 @@ def __init__( self.update_defaults_from_yaml( yamlfilename, __class__.__name__, board_name=board_name ) - + # Check target device available jtag_connected = False for c in range(self.jtag_connect_retries): @@ -54,7 +54,7 @@ def __init__( ) time.sleep(1) - #jtag_connected=True + # jtag_connected=True if not jtag_connected: raise Exception( @@ -86,7 +86,7 @@ def run_xsdb(self, cmd): ) else: vivado = os.path.join(self.custom_vivado_path, "settings64.sh") - vivado = f'. {vivado}' + vivado = f". {vivado}" if not os.path.isfile(vivado[2:]): raise Exception( "Vivado not found at: " + vivado[: -(len("settings64.sh") + 1)] @@ -95,7 +95,7 @@ def run_xsdb(self, cmd): cmd = vivado + '; xsdb -eval "{}"'.format(cmd) # cmd = [vivado + '; xsdb',' -eval "{}"'.format(cmd)] return self._shell_out2(cmd) - #return self.run_command_realtime(cmd, shell=True) == 0 + # return self.run_command_realtime(cmd, shell=True) == 0 def restart_board(self): cmd = "connect; " @@ -115,9 +115,9 @@ def tcl_errors_recover(self): def target_set_str(self, target_name): return ( - "targets -set -filter {jtag_cable_name =~ {*" - + self.jtag_cable_id - + "} && name =~ {" + "targets -set -filter {jtag_cable_name =~ {*" + + self.jtag_cable_id + + "} && name =~ {" + target_name + "}} ; " ) @@ -258,4 +258,4 @@ def microblaze_boot_linux(self, bitstream, strip): # self.run_command_realtime(cmd, shell=True) self.run_xsdb(cmd) - # 210308A3BB8D \ No newline at end of file + # 210308A3BB8D diff --git a/nebula/manager.py b/nebula/manager.py index 94e81e59..34be473a 100644 --- a/nebula/manager.py +++ b/nebula/manager.py @@ -33,7 +33,7 @@ def __init__( # noqa:C901 board_name=None, vivado_version=None, extras=None, - microblaze=False + microblaze=False, ): # Check if config info exists in yaml self.configfilename = configfilename @@ -185,7 +185,7 @@ def copy_reference_from_sdcard(self, bootbinpath, uimagepath, devtreepath): ref = ref + "/" + str(target) self.monitor[0].copy_reference(ref, target) - def network_check(self,microblaze=False): + def network_check(self, microblaze=False): if not self.net.ping_board(): if microblaze: ip = self.monitor[0].get_ip_address_microblaze() @@ -442,17 +442,16 @@ def board_reboot_jtag_uart( self.network_check() self.monitor[0].stop_log() - @_release_thread_lock + @_release_thread_lock # type: ignore def board_boot_microblaze_jtag_uart( self, - system_top_bit_path, + 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) @@ -467,7 +466,7 @@ def board_boot_microblaze_jtag_uart( if not ip: self.monitor[0].request_ip_dhcp_microblaze() ip = self.monitor[0].get_ip_address_microblaze() - self.network_check(microblaze = True) + self.network_check(microblaze=True) self.monitor[0].stop_log() @_release_thread_lock # type: ignore @@ -783,7 +782,7 @@ def run_test(self): for mon in self.monitor: mon.stop_log() - def _find_boot_files(self, folder, microblaze = False): + 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) @@ -855,7 +854,13 @@ def _find_boot_files(self, folder, microblaze = False): ) def board_reboot_auto_folder( - self, folder, sdcard=False, design_name=None, recover=False, jtag_mode=False, microblaze=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 @@ -889,7 +894,7 @@ def board_reboot_auto_folder( log.info("Found microblaze boot files:") for file in [bit, strip]: if file: - log.info(file) + log.info(file) self.board_boot_microblaze_jtag_uart( system_top_bit_path=bit, strip_path=strip, @@ -1025,4 +1030,3 @@ def verify_checksum(self, folder): self.net.verify_checksum( file_path=os.path.join("/boot", fname), reference=hash ) - diff --git a/nebula/network.py b/nebula/network.py index cd58d017..511d4583 100644 --- a/nebula/network.py +++ b/nebula/network.py @@ -9,11 +9,10 @@ import time import fabric +import paramiko.common +import paramiko.packet from fabric import Connection from paramiko import AutoAddPolicy -import paramiko.packet -import paramiko.common -import logging import nebula.errors as ne import nebula.helper as helper @@ -23,6 +22,7 @@ # This works around a known bug in older dropbear versions with large data transfers _original_read_message = paramiko.packet.Packetizer.read_message + def _patched_read_message(self): """Patched read_message that skips MAC check on verification failure""" try: @@ -30,12 +30,16 @@ def _patched_read_message(self): except paramiko.SSHException as e: if "Mismatched MAC" in str(e): # Log the error but don't fail - this is a known dropbear bug - logging.getLogger(__name__).warning(f"MAC mismatch detected (dropbear bug), continuing anyway") + logging.getLogger(__name__).warning( + "MAC mismatch detected (dropbear bug), continuing anyway" + ) # Return empty message to let the connection continue from paramiko.message import Message + return paramiko.common.MSG_IGNORE, Message() raise + paramiko.packet.Packetizer.read_message = _patched_read_message log = logging.getLogger(__name__) @@ -79,7 +83,9 @@ def __init__( ) # Apply monkey patch for MicroBlaze boards if enabled if self.microblaze_enable: - log.info("Applying Paramiko monkey patch for MicroBlaze dropbear compatibility") + log.info( + "Applying Paramiko monkey patch for MicroBlaze dropbear compatibility" + ) paramiko.packet.Packetizer.read_message = _patched_read_message else: log.info("Using standard Paramiko read_message method") @@ -106,7 +112,9 @@ def _get_connection(self): if conn.client.get_transport(): transport = conn.client.get_transport() transport.window_size = 2147483647 # Maximum window size - transport.max_packet_size = 32768 # Smaller packet size for compatibility + transport.max_packet_size = ( + 32768 # Smaller packet size for compatibility + ) transport.packetizer.REKEY_BYTES = pow(2, 30) # Delay rekeying transport.packetizer.REKEY_PACKETS = pow(2, 30) except Exception as e: @@ -158,7 +166,7 @@ def check_ssh(self): hide=True, timeout=self.ssh_timeout, pty=self.pty, - warn = True, + warn=True, in_stream=False, ) log.info("SSH successful") @@ -173,7 +181,7 @@ def check_ssh(self): if conn is not None: try: conn.close() - except: + except Exception: pass return result.failed @@ -221,7 +229,7 @@ def reboot_board(self, bypass_sleep=False): if conn is not None: try: conn.close() - except: + except Exception: pass def run_ssh_command( @@ -306,7 +314,7 @@ def copy_file_to_remote(self, src, dest): if conn is not None: try: conn.close() - except: + except Exception: pass def update_boot_partition( @@ -414,7 +422,7 @@ def _dl_file(self, filename): if conn is not None: try: conn.close() - except: + except Exception: pass def check_dmesg(self, error_on_warnings=False): @@ -435,15 +443,17 @@ def check_dmesg(self, error_on_warnings=False): if self.microblaze_enable: max_retries = 3 for attempt in range(max_retries): - log.info("dmesg command attempt: " + str(attempt +1)) + 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.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}") + log.warning(f"attempt {attempt + 1} fa iled with exception: {e}") if attempt == max_retries - 1: raise e elif self.board_name == "pluto" or self.board_name == "m2k": @@ -480,7 +490,7 @@ def check_dmesg(self, error_on_warnings=False): all_log = f.readlines() logging.info("dmesg logs collected") - #filtering known errors + # filtering known errors path = pathlib.Path(__file__).parent.absolute() res = os.path.join(path, "resources", "err_rejects.txt") with open(res) as f: @@ -504,7 +514,9 @@ def check_dmesg(self, error_on_warnings=False): log.info("Errors found in dmesg logs") 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 + 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 @@ -574,5 +586,5 @@ def monitor_dmesg(self, find_string, max_timeout_seconds=60, enable_log=True): stdout = "\n".join(stdout) log.debug(f"Full log:\n{stdout}") break - time.sleep(2) # wait a bit before returning to close ssh connection + time.sleep(2) # wait a bit before returning to close ssh connection return False diff --git a/nebula/resources/template_micro_gen.yaml b/nebula/resources/template_micro_gen.yaml index d134de43..510fe024 100644 --- a/nebula/resources/template_micro_gen.yaml +++ b/nebula/resources/template_micro_gen.yaml @@ -261,4 +261,4 @@ microblaze-config: default: True options: ["True", "False"] help: "Issue 'con' automatically after ELF download" - optional: True \ No newline at end of file + optional: True diff --git a/nebula/tasks.py b/nebula/tasks.py index 906a78e3..97f6a07e 100644 --- a/nebula/tasks.py +++ b/nebula/tasks.py @@ -900,7 +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" + "microblaze": "set this flag for Microblaze boards", }, ) def update_boot_files_manager( @@ -916,7 +916,9 @@ def update_boot_files_manager( microblaze=False, ): """Update boot files through u-boot menu (Assuming board is running)""" - m = nebula.manager(configfilename=yamlfilename, board_name=board_name, microblaze=microblaze) + m = nebula.manager( + configfilename=yamlfilename, board_name=board_name, microblaze=microblaze + ) if not folder: m.board_reboot_auto( @@ -927,7 +929,9 @@ def update_boot_files_manager( sdcard=sdcard, ) else: - m.board_reboot_auto_folder(folder=folder, sdcard=sdcard, design_name=board_name, microblaze=microblaze) + m.board_reboot_auto_folder( + folder=folder, sdcard=sdcard, design_name=board_name, microblaze=microblaze + ) @task( @@ -1272,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 71ef2a26..8ef06a5d 100644 --- a/nebula/uart.py +++ b/nebula/uart.py @@ -460,10 +460,12 @@ def get_local_mac_usbdev(self): """Read MAC Address of enumerated NIC on host from DUT (Pluto/M2K only)""" 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") + 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) @@ -487,7 +489,7 @@ def get_ip_address_microblaze(self): self._write_data(cmd) data = self._read_for_time(period=2) log.info("Data read: " + str(data)) - if restart: + if restart: self.start_log(logappend=True) for d in data: if isinstance(d, list): @@ -667,7 +669,6 @@ def _wait_for_boot_complete_linaro(self, done_string="Welcome to Linaro 14.04"): if restart: 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.""" @@ -681,13 +682,15 @@ def _wait_for_boot_complete_microblaze(self, max_time=10, prompt="#"): ) log.info("UART read after ENTER: {}".format(found)) if found: - log.info("---buildroot welcome message found, waiting for login prompt---") + 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): @@ -709,16 +712,16 @@ def _wait_for_boot_complete_microblaze(self, max_time=10, prompt="#"): if not login_success: log.warning("Login failed") return False - - #wait for MicroBlaze shell prompt + + # 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 l in lines: - if prompt in l: + for uart_line in lines: + if prompt in uart_line: log.info("Microblaze prompt found, boot complete") isRootLevel = True break @@ -728,7 +731,7 @@ def _wait_for_boot_complete_microblaze(self, max_time=10, prompt="#"): 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 @@ -782,7 +785,7 @@ def load_system_uart_from_tftp(self): """Load complete system (bitstream, devtree, kernel) during uboot from TFTP""" restart = False - if self.listen_thread_run: + if self.listen_thread_run: restart = True self.stop_log() diff --git a/tests/microblaze_test.py b/tests/microblaze_test.py index a5e405d0..fca285c5 100644 --- a/tests/microblaze_test.py +++ b/tests/microblaze_test.py @@ -1,19 +1,22 @@ import os +import shutil import time + import pytest -import shutil -from nebula import downloader -from nebula import network -import nebula +import nebula +from nebula import downloader, network here = os.path.dirname(os.path.abspath(__file__)) -cfg = os.path.join(here, "nebula_config","microblaze.yaml") +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) +manager = nebula.manager( + configfilename=cfg, board_name="kcu105_adrv9371x", microblaze=True +) + def test_microblaze_downloader(): # Clean output folder @@ -30,9 +33,12 @@ def test_microblaze_downloader(): ) 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/", "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")) + # assert os.path.isfile(os.path.join("/root/wk_ace/nebula/outs/", "properties.yaml")) + def test_microblaze_boot(): # Use files from outs folder @@ -41,20 +47,22 @@ def test_microblaze_boot(): 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 + manager.monitor[0].logfilename = os.path.join( + log_folder, "kcu105_adrv9371x" + ) # Flush + + # boot microblaze board + manager.board_reboot_auto_folder(out_folder, microblaze=True) - #boot microblaze board - manager.board_reboot_auto_folder( - out_folder, - microblaze=True - ) -#@pytest.mark.stress +# @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) + # 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() - \ No newline at end of file diff --git a/tests/test_downloader.py b/tests/test_downloader.py index bf5d8613..06cadde3 100644 --- a/tests/test_downloader.py +++ b/tests/test_downloader.py @@ -119,21 +119,25 @@ def test_noos_downloader(test_downloader, board_name, branch, filetype): assert os.path.isfile("outs/hashes.txt") -#@pytest.mark.skip(reason="Not built") #remove for testing +# @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): 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") - + 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, @@ -142,13 +146,13 @@ def test_microblaze_downloader(test_downloader, board_name, branch, filetype): 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/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") From df1d38edfe8ca4a73f216e1aa376d6e10c414c00 Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Mon, 19 Jan 2026 08:19:24 +0800 Subject: [PATCH 24/28] removed Monkey Patch from Paramiko SSH connection --- nebula/network.py | 204 ++++++++-------------------------------------- 1 file changed, 33 insertions(+), 171 deletions(-) diff --git a/nebula/network.py b/nebula/network.py index 511d4583..627e6dc2 100644 --- a/nebula/network.py +++ b/nebula/network.py @@ -9,39 +9,12 @@ import time import fabric -import paramiko.common -import paramiko.packet from fabric import Connection -from paramiko import AutoAddPolicy import nebula.errors as ne import nebula.helper as helper from nebula.common import utils -# Monkey-patch Paramiko to skip MAC verification for dropbear compatibility -# This works around a known bug in older dropbear versions with large data transfers -_original_read_message = paramiko.packet.Packetizer.read_message - - -def _patched_read_message(self): - """Patched read_message that skips MAC check on verification failure""" - try: - return _original_read_message(self) - except paramiko.SSHException as e: - if "Mismatched MAC" in str(e): - # Log the error but don't fail - this is a known dropbear bug - logging.getLogger(__name__).warning( - "MAC mismatch detected (dropbear bug), continuing anyway" - ) - # Return empty message to let the connection continue - from paramiko.message import Message - - return paramiko.common.MSG_IGNORE, Message() - raise - - -paramiko.packet.Packetizer.read_message = _patched_read_message - log = logging.getLogger(__name__) @@ -76,51 +49,10 @@ def __init__( self.dhcp = False self.ssh_timeout = 30 self.board_name = board_name - self.pty = True # Use PTY mode to avoid MAC mismatch errors with dropbear self.microblaze_enable = any( keyword in self.board_name.lower() for keyword in ["kcu", "vc", "kc"] ) - # Apply monkey patch for MicroBlaze boards if enabled - if self.microblaze_enable: - log.info( - "Applying Paramiko monkey patch for MicroBlaze dropbear compatibility" - ) - paramiko.packet.Packetizer.read_message = _patched_read_message - else: - log.info("Using standard Paramiko read_message method") - paramiko.packet.Packetizer.read_message = _original_read_message - - def _get_connection(self): - """Create a fabric Connection with host key checking disabled""" - conn = Connection( - self.dutusername + "@" + self.dutip, - connect_kwargs={ - "password": self.dutpassword, - "allow_agent": False, - "look_for_keys": False, - "disabled_algorithms": {}, # Ensure no algorithms are disabled - "banner_timeout": 60, # Increase banner timeout for slow connections - }, - ) - # Disable SSH host key checking by setting policy to auto-add - try: - conn.open() - if conn.client: - conn.client.set_missing_host_key_policy(AutoAddPolicy()) - # Set transport options for better compatibility with dropbear - if conn.client.get_transport(): - transport = conn.client.get_transport() - transport.window_size = 2147483647 # Maximum window size - transport.max_packet_size = ( - 32768 # Smaller packet size for compatibility - ) - transport.packetizer.REKEY_BYTES = pow(2, 30) # Delay rekeying - transport.packetizer.REKEY_PACKETS = pow(2, 30) - except Exception as e: - log.error(f"Failed to establish SSH connection: {e}") - raise - return conn def ping_board(self, tries=10): """Ping board and check if any received @@ -157,32 +89,24 @@ def check_ssh(self): """ retries = 3 for t in range(retries): - conn = None try: log.info("Checking for board through SSH") - conn = self._get_connection() - result = conn.run( + result = fabric.Connection( + self.dutusername + "@" + self.dutip, + connect_kwargs={"password": self.dutpassword}, + ).run( "uname -a", hide=True, timeout=self.ssh_timeout, - pty=self.pty, - warn=True, + pty=True, in_stream=False, ) - log.info("SSH successful") break except Exception as ex: log.warning("Exception raised: " + str(ex)) time.sleep(3) if t >= (retries - 1): - log.error("SSH failed after all retries") raise Exception("SSH Failed") - finally: - if conn is not None: - try: - conn.close() - except Exception: - pass return result.failed def check_board_booted(self): @@ -201,36 +125,28 @@ 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 - ex = None for t in range(retries): - conn = None try: - conn = self._get_connection() - result = conn.run("/sbin/reboot", hide=True, pty=self.pty) + result = fabric.Connection( + self.dutusername + "@" + self.dutip, + connect_kwargs={"password": self.dutpassword}, + ).run("/sbin/reboot", hide=False) if result.ok: print("Rebooting board with SSH") if not bypass_sleep: time.sleep(30) break else: - # Use PDU + # TODO: Use PDU raise Exception("PDU reset not implemented yet") - except Exception as inner_ex: - ex = inner_ex + except Exception as ex: log.warning("Exception raised: " + str(ex)) time.sleep(3) if t >= (retries - 1): raise Exception("Exception occurred during SSH Reboot", str(ex)) - finally: - if conn is not None: - try: - conn.close() - except Exception: - pass def run_ssh_command( self, @@ -242,24 +158,23 @@ def run_ssh_command( ): result = None filename = None - ex = None for t in range(retries): log.info( "ssh command:" + command + " to " + self.dutusername + "@" + self.dutip ) - conn = None try: - conn = self._get_connection() - result = conn.run( + result = fabric.Connection( + self.dutusername + "@" + self.dutip, + connect_kwargs={"password": self.dutpassword}, + ).run( command, hide=True, timeout=self.ssh_timeout, - pty=self.pty, # Use PTY to avoid MAC mismatch errors with dropbear + pty=True, in_stream=False, ) if result.failed: - log.error(f"Command failed: {command}") raise Exception("Failed to run command:", command) if print_result_to_file: @@ -281,41 +196,29 @@ def run_ssh_command( with open(f"{self.board_name}_err_{filename}.log", "w") as f: f.write(result.stderr) break - except Exception as inner_ex: - ex = inner_ex - log.warning("Exception raised: " + str(ex), exc_info=True) - if not ignore_exceptions and t >= (retries - 1): - raise Exception("SSH Failed: " + str(ex)) - finally: - if conn is not None: - try: - conn.close() - except Exception as close_ex: - log.warning("Failed to close connection: " + str(close_ex)) + except Exception as ex: + log.warning("Exception raised: " + str(ex)) + if not ignore_exceptions: + time.sleep(3) + if t >= (retries - 1): + raise Exception("SSH Failed: " + str(ex)) + return result def copy_file_to_remote(self, src, dest): retries = 3 - ex = None log.info("Copying file to remote: " + src) for t in range(retries): - conn = None try: - conn = self._get_connection() - conn.put(src, remote=dest) - break - except Exception as inner_ex: - ex = inner_ex + Connection( + self.dutusername + "@" + self.dutip, + connect_kwargs={"password": self.dutpassword}, + ).put(src, remote=dest) + except Exception as ex: log.warning("Exception raised: " + str(ex)) time.sleep(3) if t >= (retries - 1): raise ne.SSHError - finally: - if conn is not None: - try: - conn.close() - except Exception: - pass def update_boot_partition( self, @@ -414,16 +317,10 @@ def update_boot_partition_existing_files(self, subfolder=None): self.run_ssh_command("sudo reboot", ignore_exceptions=True) def _dl_file(self, filename): - conn = None - try: - conn = self._get_connection() - conn.get(filename) - finally: - if conn is not None: - try: - conn.close() - except Exception: - pass + fabric.Connection( + self.dutusername + "@" + self.dutip, + connect_kwargs={"password": self.dutpassword}, + ).get(filename) def check_dmesg(self, error_on_warnings=False): """check_dmesg: @@ -453,7 +350,7 @@ def check_dmesg(self, error_on_warnings=False): self._dl_file(tmp_filename_err) break except Exception as e: - log.warning(f"attempt {attempt + 1} fa iled with exception: {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": @@ -553,38 +450,3 @@ def verify_checksum(self, file_path, reference, algo="sha256"): f"Checksum does not match for {file_path}:\ Ref: {reference} Actual: {result.stdout.strip()}" ) - - def monitor_dmesg(self, find_string, max_timeout_seconds=60, enable_log=True): - """monitor_dmesg: - Monitor dmesg for specific strings - - return: - status: 0 if no errors found, 1 otherwise - """ - if not isinstance(find_string, str): - raise Exception("find_string must be a string") - log.info(f"Monitoring dmesg for string {find_string}") - start_time = time.time() - connection = self._get_connection() - with connection as c: - while True: - time.sleep(5) - dmesg_stream = c.run("dmesg", hide=True, pty=self.pty) - if not isinstance(dmesg_stream.stdout, list): - stdout = dmesg_stream.stdout.splitlines() - else: - stdout = dmesg_stream.stdout - for line in stdout: - if enable_log: - log.info(f"Got: {line}") - if find_string in line: - log.info(f"Found string {find_string} in dmesg") - return True - if (time.time() - start_time) > max_timeout_seconds: - log.info(f"Timeout waiting for string {find_string} in dmesg") - if isinstance(stdout, list): - stdout = "\n".join(stdout) - log.debug(f"Full log:\n{stdout}") - break - time.sleep(2) # wait a bit before returning to close ssh connection - return False From 8314540b72e6390e82a88e6fd2372fb1b30f9439 Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Thu, 22 Jan 2026 09:53:04 +0800 Subject: [PATCH 25/28] Code cleanup Signed-off-by: Zvckkk --- nebula/downloader.py | 36 ------------------------------------ nebula/jtag.py | 8 +------- nebula/network.py | 2 +- nebula/uart.py | 1 - 4 files changed, 2 insertions(+), 45 deletions(-) diff --git a/nebula/downloader.py b/nebula/downloader.py index 722d9590..7ce41a44 100644 --- a/nebula/downloader.py +++ b/nebula/downloader.py @@ -775,7 +775,6 @@ def _get_files_boot_partition( if microblaze: design_source_root = f"microblaze_images/{design_name}" print(f"DEBUG: MicroBlaze design_source_root: {design_source_root}") - print # get simpleImage log.info("Getting simpleimage") @@ -971,37 +970,6 @@ def _get_files_linux( else: url_template = "https://{}/artifactory/sdg-generic-development/linux/releases/{}/{}/{}" - # if microblaze: - # if branch == "main": - # url_template = ( - # "https://{}/artifactory/sdg-generic-development/boot_partition/{}/{}/{}") - # design_source_root = f"microblaze_images/{design_name}" - # print(f"DEBUG: MicroBlaze design_source_root: {design_source_root}") - # # 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: - - # Get files from linux folder # Get kernel log.info("Getting " + kernel) self._get_file( @@ -1207,10 +1175,6 @@ def _get_files( ) if microblaze: - # hdl_branch = "master" if branch == "main" else branch - # self._get_files_hdl( - # hdl_folder, source, source_root, hdl_branch, hdl_output=True - # ) self._get_files_boot_partition( reference_boot_folder, devicetree_subfolder, diff --git a/nebula/jtag.py b/nebula/jtag.py index 423b6587..91ec1986 100644 --- a/nebula/jtag.py +++ b/nebula/jtag.py @@ -54,8 +54,6 @@ def __init__( ) time.sleep(1) - # jtag_connected=True - if not jtag_connected: raise Exception( "JTAG connection cannot find target HW: {}".format(self.jtag_cable_id) @@ -86,16 +84,13 @@ def run_xsdb(self, cmd): ) else: vivado = os.path.join(self.custom_vivado_path, "settings64.sh") - vivado = f". {vivado}" if not os.path.isfile(vivado[2:]): raise Exception( "Vivado not found at: " + vivado[: -(len("settings64.sh") + 1)] ) cmd = vivado + '; xsdb -eval "{}"'.format(cmd) - # cmd = [vivado + '; xsdb',' -eval "{}"'.format(cmd)] return self._shell_out2(cmd) - # return self.run_command_realtime(cmd, shell=True) == 0 def restart_board(self): cmd = "connect; " @@ -256,6 +251,5 @@ def microblaze_boot_linux(self, bitstream, strip): cmd += "con; " cmd += "after 3000; " - # self.run_command_realtime(cmd, shell=True) self.run_xsdb(cmd) - # 210308A3BB8D + diff --git a/nebula/network.py b/nebula/network.py index 627e6dc2..fe6103e9 100644 --- a/nebula/network.py +++ b/nebula/network.py @@ -51,7 +51,7 @@ def __init__( self.board_name = board_name self.microblaze_enable = any( - keyword in self.board_name.lower() for keyword in ["kcu", "vc", "kc"] + keyword in self.board_name.lower() for keyword in ["kcu", "vc"] ) def ping_board(self, tries=10): diff --git a/nebula/uart.py b/nebula/uart.py index 8ef06a5d..2a5020d3 100644 --- a/nebula/uart.py +++ b/nebula/uart.py @@ -476,7 +476,6 @@ def request_ip_dhcp_microblaze(self, nic="eth0"): def get_ip_address_microblaze(self): """Read IP address of DUT using ifconfig from UART for MicroBlaze""" - # cmd = "ifconfig eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127" cmd = "ifconfig eth0 | grep -v 127 | awk '$1 == \"inet\" {print $2}' | awk -F'/' '{print $1}'" restart = False if self.listen_thread_run: From 23c37e85bbecfc0cd25e19f3662f365cfd9bce4c Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Fri, 23 Jan 2026 08:17:43 +0800 Subject: [PATCH 26/28] Fixed Lints Signed-off-by: Zvckkk --- nebula/jtag.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nebula/jtag.py b/nebula/jtag.py index 91ec1986..a2b7ae4f 100644 --- a/nebula/jtag.py +++ b/nebula/jtag.py @@ -252,4 +252,3 @@ def microblaze_boot_linux(self, bitstream, strip): cmd += "after 3000; " self.run_xsdb(cmd) - From f53c4bc318da1e88ecb30d0b0b4901a0080878b8 Mon Sep 17 00:00:00 2001 From: Ace Alexander Ilog Date: Wed, 11 Feb 2026 10:56:48 +0800 Subject: [PATCH 27/28] logic update for microblaze_enable flag Signed-off-by: Ace Alexander Ilog --- nebula/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nebula/network.py b/nebula/network.py index fe6103e9..15321b94 100644 --- a/nebula/network.py +++ b/nebula/network.py @@ -51,7 +51,7 @@ def __init__( self.board_name = board_name self.microblaze_enable = any( - keyword in self.board_name.lower() for keyword in ["kcu", "vc"] + keyword in self.board_name.lower() for keyword in ["vc", "kc"] ) def ping_board(self, tries=10): From 765d7f885b21ca5a201e732da8c5b2ddd86bad51 Mon Sep 17 00:00:00 2001 From: Zvckkk Date: Mon, 20 Apr 2026 13:52:42 +0800 Subject: [PATCH 28/28] changed bearer token parameter Signed-off-by: Zvckkk --- nebula/downloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nebula/downloader.py b/nebula/downloader.py index 7ce41a44..43752492 100644 --- a/nebula/downloader.py +++ b/nebula/downloader.py @@ -617,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", }