From e2382c643bf2faaabeb17f0022691c5b27db6174 Mon Sep 17 00:00:00 2001 From: Niklas Eiling Date: Wed, 13 Dec 2023 14:22:48 +0100 Subject: [PATCH] add hwdef-parse script from hardware repo Signed-off-by: Niklas Eiling --- fpga/scripts/hwdef-parse.py | 313 ++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 fpga/scripts/hwdef-parse.py diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py new file mode 100644 index 000000000..14a552eac --- /dev/null +++ b/fpga/scripts/hwdef-parse.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +""" +Author: Steffen Vogel +Author: Daniel Krebs +Author: Hatim Kanchwala +Author: Pascal Bauer +Copyright: 2017-2022, Institute for Automation of Complex Power Systems, EONERC +License: GNU General Public License (version 3) + +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', '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)) +