#!/usr/bin/env python3 """ HWH File Parser Author: Steffen Vogel Author: Daniel Krebs Author: Hatim Kanchwala Author: Pascal Bauer SPDX-FileCopyrightText: 2017-2022 Steffen Vogel SPDX-FileCopyrightText: 2017-2022 Daniel Krebs SPDX-FileCopyrightText: 2017-2022 Hatim Kanchwala SPDX-FileCopyrightText: 2023 Pascal Bauer SPDX-License-Identifier: GPL-3.0-or-later This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ from multiprocessing.sharedctypes import Value from lxml import etree import zipfile import sys import re import json whitelist = [ [ 'xilinx.com', 'ip', 'zynq_ultra_ps_e' ], [ 'xilinx.com', 'ip', 'axi_timer' ], [ 'xilinx.com', 'ip', 'axis_switch' ], [ 'xilinx.com', 'ip', 'axi_fifo_mm_s' ], [ 'xilinx.com', 'ip', 'axi_dma' ], [ 'xilinx.com', 'ip', 'aurora_8b10b' ], [ 'xilinx.com', 'ip', 'axi_gpio' ], [ 'xilinx.com', 'ip', 'axi_bram_ctrl' ], [ 'xilinx.com', 'ip', 'axi_pcie' ], [ 'xilinx.com', 'ip', 'axi_iic' ], [ 'xilinx.com', 'module_ref', 'dinoif_fast' ], [ 'xilinx.com', 'module_ref', 'dinoif_dac' ], [ 'xilinx.com', 'module_ref', 'axi_pcie_intc' ], [ 'xilinx.com', 'hls', 'rtds2gpu' ], [ 'xilinx.com', 'hls', 'mem' ], [ 'acs.eonerc.rwth-aachen.de', 'user', 'axi_pcie_intc' ], [ 'acs.eonerc.rwth-aachen.de', 'user', 'rtds_axis' ], [ 'acs.eonerc.rwth-aachen.de', 'user', 'aurora_axis' ], [ 'acs.eonerc.rwth-aachen.de', 'hls' ], [ 'acs.eonerc.rwth-aachen.de', 'sysgen' ] ] # List of VLNI ids of AXI4-Stream infrastructure IP cores which do not alter data # see PG085 (AXI4-Stream Infrastructure IP Suite v2.2) axi_converter_whitelist = [ [ 'xilinx.com', 'ip', 'axis_subset_converter' ], [ 'xilinx.com', 'ip', 'axis_clock_converter' ], [ 'xilinx.com', 'ip', 'axis_register_slice' ], [ 'xilinx.com', 'ip', 'axis_dwidth_converter' ], [ 'xilinx.com', 'ip', 'axis_register_slice' ], [ 'xilinx.com', 'ip', 'axis_data_fifo' ] ] opponent = { 'MASTER' : ('SLAVE', 'TARGET'), 'SLAVE' : ('MASTER', 'INITIATOR'), 'INITIATOR' : ('TARGET', 'SLAVE'), 'TARGET' : ('INITIATOR', 'MASTER') } def bus_trace(root, busname, type, whitelist): module = root.xpath('.//MODULE[.//BUSINTERFACE[@BUSNAME="{}" and (@TYPE="{}" or @TYPE="{}")]]'.format(busname, type[0], type[1])) vlnv = module[0].get('VLNV') instance = module[0].get('INSTANCE') if vlnv_match(vlnv, whitelist): return instance, busname elif vlnv_match(vlnv, axi_converter_whitelist): next_bus = module[0].xpath('.//BUSINTERFACE[@TYPE="{}" or @TYPE="{}"]'.format(opponent[type[0]][0], opponent[type[0]][1])) next_busname = next_bus[0].get('BUSNAME') return bus_trace(root, next_busname, type, whitelist) else: raise TypeError('Unsupported AXI4-Stream IP core: %s (%s)' % (instance, vlnv)) def vlnv_match(vlnv, whitelist): c = vlnv.split(':') for w in whitelist: if c[:len(w)] == w: return True return False def remove_prefix(text, prefix): return text[text.startswith(prefix) and len(prefix):] def sanitize_name(name): name = remove_prefix(name, 'S_') name = remove_prefix(name, 'M_') name = remove_prefix(name, 'AXI_') name = remove_prefix(name, 'AXIS_') return name if len(sys.argv) < 2: print('Usage: {} path/to/*.hwdef'.format(sys.argv[0])) print(' {} path/to/*.hwh'.format(sys.argv[0])) sys.exit(1) try: # read .hwdef which is actually a zip-file zip = zipfile.ZipFile(sys.argv[1], 'r') hwh = zip.read('top.hwh') except: f = open(sys.argv[1], 'r') hwh = f.read() # parse .hwh file which is actually XML try: root = etree.XML(hwh) except: print('Bad format of "{}"! Did you choose the right file?'.format(sys.argv[1])) sys.exit(1) ips = {} # find all whitelisted modules modules = root.find('.//MODULES') for module in modules: instance = module.get('INSTANCE') vlnv = module.get('VLNV') # Ignroing unkown if not vlnv_match(vlnv, whitelist): continue ips[instance] = { 'vlnv' : vlnv } # populate parameters params = module.find('.//PARAMETERS') if params is not None and instance != "zynq_ultra_ps_e_0": #! Parameters of "zynq" ignored p = ips[instance].setdefault('parameters', {}) for param in params: name = param.get('NAME').lower() value = param.get('VALUE') try: value = int(value, 0) except ValueError: pass p[name] = value # populate memory view mmap = module.find('.//MEMORYMAP') if mmap is None: continue mem = ips[instance].setdefault('memory-view', {}) for mrange in mmap: mem_interface = mrange.get('MASTERBUSINTERFACE') mem_instance = mrange.get('INSTANCE') mem_block = mrange.get('ADDRESSBLOCK') _interface = mem.setdefault(mem_interface, {}) _instance = _interface.setdefault(mem_instance, {}) _block = _instance.setdefault(mem_block, {}) _block['baseaddr'] = int(mrange.get('BASEVALUE'), 16) _block['highaddr'] = int(mrange.get('HIGHVALUE'), 16) _block['size'] = _block['highaddr'] - _block['baseaddr'] + 1 # find AXI-Stream switch port mapping switch = root.find('.//MODULE[@MODTYPE="axis_switch"]') busifs = switch.find('.//BUSINTERFACES') switch_ports = 0 for busif in busifs: if busif.get('VLNV') != 'xilinx.com:interface:axis:1.0': continue switch_ports += 1 busname = busif.get('BUSNAME') name = busif.get('NAME') type = busif.get('TYPE') r = re.compile('(M|S)([0-9]+)_AXIS') m = r.search(name) port = int(m.group(2)) switch_ip_ports = ips[switch.get('INSTANCE')].setdefault('ports', []) ep, busname_ep = bus_trace(root, busname, opponent[type], whitelist) if ep in ips: ports = ips[ep].setdefault('ports', []) ports.append({ 'role': opponent[type][0].lower(), 'target': '{}:{}'.format(switch.get('INSTANCE'), name) }) module_ep = root.find('.//MODULE[@INSTANCE="{}"]'.format(ep)) busif_ep = module_ep.find('.//BUSINTERFACE[@BUSNAME="{}"]'.format(busname_ep)) if busif_ep is None: print("cannot find businterface: {}".format(busname_ep)) sys.exit(1) busif_name = ports[-1]['name'] = sanitize_name(busif_ep.get('NAME')) ports[-1]['name'] = busif_name switch_ip_ports.append({ 'role': type.lower(), 'target': '{}:{}'.format(ep, busif_name), 'name': name }) # set number of master/slave port pairs for switch ips[switch.get('INSTANCE')]['num_ports'] = int(switch_ports / 2) # find interrupt assignments intc = root.find('.//MODULE[@MODTYPE="axi_pcie_intc"]') if intc is not None: intr = intc.xpath('.//PORT[@NAME="intr" and @DIR="I"]')[0] concat = root.xpath('.//MODULE[@MODTYPE="xlconcat" and .//PORT[@SIGNAME="{}" and @DIR="O"]]'.format(intr.get('SIGNAME')))[0] ports = concat.xpath('.//PORT[@DIR="I"]') for port in ports: name = port.get('NAME') signame = port.get('SIGNAME') # Skip unconnected IRQs if not signame: continue r = re.compile('In([0-9+])') m = r.search(name) irq = int(m.group(1)) ip = root.xpath('.//MODULE[.//PORT[@SIGNAME="{}" and @DIR="O"]]'.format(signame))[0] instance = ip.get('INSTANCE') vlnv = ip.get('VLNV') modtype = ip.get('MODTYPE') originators = [] # follow one level of OR gates merging interrupts (may be generalized later) if modtype == 'util_vector_logic': logic_op = ip.xpath('.//PARAMETER[@NAME="C_OPERATION"]')[0] if logic_op.get('VALUE') == 'or': # hardware interrupts sharing the same IRQ at the controller ports = ip.xpath('.//PORT[@DIR="I"]') for port in ports: signame = port.get('SIGNAME') ip = root.xpath('.//MODULE[.//PORT[@SIGNAME="{}" and @DIR="O"]]'.format(signame))[0] instance = ip.get('INSTANCE') originators.append((instance, signame)) else: # consider this instance as originator originators.append((instance, signame)) for instance, signame in originators: ip = root.xpath('.//MODULE[.//PORT[@SIGNAME="{}" and @DIR="O"]]'.format(signame))[0] port = ip.xpath('.//PORT[@SIGNAME="{}" and @DIR="O"]'.format(signame))[0] irqname = port.get('NAME') if instance in ips: irqs = ips[instance].setdefault('irqs', {}) irqs[irqname] = '{}:{}'.format(intc.get('INSTANCE'), irq) # Find BRAM storage depths (size) brams = root.xpath('.//MODULE[@MODTYPE="axi_bram_ctrl"]') for bram in brams: instance = bram.get('INSTANCE') width = bram.find('.//PARAMETER[@NAME="DATA_WIDTH"]').get('VALUE') depth = bram.find('.//PARAMETER[@NAME="MEM_DEPTH"]').get('VALUE') size = int(width) * int(depth) / 8 if instance in ips: ips[instance]['size'] = int(size) pcies = root.xpath('.//MODULE[@MODTYPE="axi_pcie"]') for pcie in pcies: instance = pcie.get('INSTANCE') axi_bars = ips[instance].setdefault('axi_bars', {}) pcie_bars = ips[instance].setdefault('pcie_bars', {}) for from_bar, to_bar, from_bars in (('AXIBAR', 'PCIEBAR', axi_bars), ('PCIEBAR', 'AXIBAR', pcie_bars)): from_bar_num = int(pcie.find('.//PARAMETER[@NAME="C_{}_NUM"]'.format(from_bar)).get('VALUE')) for i in range(0, from_bar_num): from_bar_to_bar_offset = int(pcie.find('.//PARAMETER[@NAME="C_{}2{}_{}"]'.format(from_bar, to_bar, i)).get('VALUE'), 16) from_bars['BAR{}'.format(i)] = { 'translation': from_bar_to_bar_offset } if from_bar == 'AXIBAR': axi_bar_lo = int(pcie.find('.//PARAMETER[@NAME="C_{}_{}"]'.format(from_bar, i)).get('VALUE'), 16) axi_bar_hi = int(pcie.find('.//PARAMETER[@NAME="C_{}_HIGHADDR_{}"]'.format(from_bar, i)).get('VALUE'), 16) axi_bar_size = axi_bar_hi - axi_bar_lo + 1 axi_bar = from_bars['BAR{}'.format(i)] axi_bar['baseaddr'] = axi_bar_lo axi_bar['highaddr'] = axi_bar_hi axi_bar['size'] = axi_bar_size print(json.dumps(ips, indent=2))