Source code for pynq.pl_server.tcl_parser

#   Copyright (c) 2016, Xilinx, Inc.
#   All rights reserved.
#
#   Redistribution and use in source and binary forms, with or without
#   modification, are permitted provided that the following conditions are met:
#
#   1.  Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#
#   2.  Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#
#   3.  Neither the name of the copyright holder nor the names of its
#       contributors may be used to endorse or promote products derived from
#       this software without specific prior written permission.
#
#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
#   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#   PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
#   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
#   OR BUSINESS INTERRUPTION). HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
#   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
#   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


import abc
import os
import re
from copy import deepcopy
from pynq.ps import CPU_ARCH_IS_SUPPORTED, CPU_ARCH, ZYNQ_ARCH, ZU_ARCH

__author__ = "Yun Rock Qu"
__copyright__ = "Copyright 2016, Xilinx"
__email__ = "pynq_support@xilinx.com"


[docs]def get_tcl_name(bitfile_name): """This method returns the name of the tcl file. For example, the input "/home/xilinx/pynq/overlays/base/base.bit" will lead to the result "/home/xilinx/pynq/overlays/base/base.tcl". Parameters ---------- bitfile_name : str The absolute path of the .bit file. Returns ------- str The absolute path of the .tcl file. """ return os.path.splitext(bitfile_name)[0] + '.tcl'
class _TCLABC(metaclass=abc.ABCMeta): """Helper Class to extract information from a TCL configuration file. Note ---- This class requires the absolute path of the '.tcl' file. Attributes ---------- ip_dict : dict All the addressable IPs from PS7. Key is the name of the IP; value is a dictionary mapping the physical address, address range, IP type, memory segment ID, the state associated with that IP, any interrupts and GPIO pins attached to the IP and the full path to the IP in the block design: {str: {'phys_addr' : int, 'addr_range' : int,\ 'type' : str, 'mem_id' : str, 'state' : str,\ 'interrupts' : dict, 'gpio' : dict, 'fullpath' : str}}. gpio_dict : dict All the GPIO pins controlled by PS7. Key is the name of the GPIO pin; value is a dictionary mapping user index (starting from 0), the state associated with that GPIO pin and the pins in block diagram attached to the GPIO: {str: {'index' : int, 'state' : str, 'pins' : [str]}}. interrupt_controllers : dict All AXI interrupt controllers in the system attached to a PS7 interrupt line. Key is the name of the controller; value is a dictionary mapping parent interrupt controller and the line index of this interrupt: {str: {'parent': str, 'index' : int}}. The PS7 is the root of the hierarchy and is unnamed. interrupt_pins : dict All pins in the design attached to an interrupt controller. Key is the name of the pin; value is a dictionary mapping the interrupt controller and the line index used: {str: {'controller' : str, 'index' : int}}. hierarchy_dict : dict All of the hierarchies in the block design containing addressable IP. The keys are the hiearachies and the values are dictionaries containing the IP and sub-hierarchies contained in the hierarchy and and GPIO and interrupts attached to the hierarchy. The keys in dictionaries are relative to the hierarchy and the ip dict only contains immediately contained IP - not those in sub-hierarchies. {str: {'ip': dict, 'hierarchies': dict, 'interrupts': dict,\ 'gpio': dict, 'fullpath': str}} """ # common key strings to search for in the TCL file hier_use_pat = "create_hier_cell" hier_proc_def_pat = "proc {}".format(hier_use_pat) hier_def_regex = "create_hier_cell_(?P<name>[^ ]*)" hier_proc_end_pat = "}\n" hier_use_regex = ("create_hier_cell_(?P<hier_name>[^ ]*) ([^ ].*) " + "(?P<instance_name>[^ ]*)\n") config_ip_pat = "CONFIG" config_ignore_pat = ".VALUE_SRC" config_regex = "CONFIG.(?P<key>.+?) \{(?P<value>.+?)\}" prop_start_pat = "set_property -dict [" prop_end_inst_regex = "\] \$(?P<instance_name>.+?)$" prop_end_nets_regex = "\] \[.*\]" net_pat = "connect_bd_net -net" net_regex = "\[get_bd_pins (?P<name>[^]]+)\]" addr_pat = "create_bd_addr_seg" ip_pat = "create_bd_cell -type ip -vlnv " ip_regex = ("create_bd_cell -type ip -vlnv " + "(?P<author>.+?):" + "(?P<type>.+?):" + "(?P<ip_name>.+?):" + "(?P<version>.+?) " + "(?P<instance_name>[^ ]*)") ip_block_name_pat = "set block_name" ip_block_name_regex = "set block_name (?P<ip_block_name>.+)" ip_block_pat = "create_bd_cell -type module -reference " ip_block_regex = ("set (?P<instance_name>.*) " + "\[create_bd_cell -type module -reference " + "(?P<block_name>[\S]*) " + "(?P<block_cell_name>[\S]*)\]") ignore_regex = "\s*(\#|catch).*" # following members will be overridden in the child classes ps_ip_name = "" irq_pin_offset = 0 irq_pin_name = "" gpio_pin_name = "" clk_odiv_regex = "" clk_enable_regex = "" pl_clks = [] clock_dict = {} def __init__(self, tcl_name): """Returns a map built from the supplied tcl file Parameters --------- tcl_name : str The tcl filename to parse. This is opened directly so should be fully qualified Note ---- If this method is called on an unsupported architecture it will warn and return without initialization """ # Regex Variable updated during processing addr_regex = "create_bd_addr_seg " +\ "-range (?P<range>0[xX][0-9a-fA-F]+) " +\ "-offset (?P<addr>0[xX][0-9a-fA-F]+) " +\ "\[get_bd_addr_spaces ([^ ].*) " +\ "\[get_bd_addr_segs (?P<hier>.+?)\] " +\ "(?P<name>[A-Za-z0-9_]+)" # Initialize result variables self.partial = True self.intc_names = [] self.interrupt_controllers = {} self.concat_cells = {} self.nets = [] self.pins = {} self.prop = [] self.interrupt_pins = {} self.ps_name = "" self.ip_dict = {} self.gpio_dict = {} self.mem_dict = {} # Parsing state current_hier = "" last_concat = "" ip_block_name = "" in_prop = False gpio_idx = None gpio_dict = dict() hier_dict = dict() hier_dict[current_hier] = dict() with open(tcl_name, 'r') as f: for line in f: if re.match(self.ignore_regex, line): continue # Matching IP configurations elif self.prop_start_pat in line: in_prop = True # Matching IP block name elif self.ip_block_name_pat in line: m = re.search(self.ip_block_name_regex, line, re.IGNORECASE) ip_block_name = m.group("ip_block_name") # Matching Property declarations elif in_prop: if (re.search(self.prop_end_inst_regex, line, re.IGNORECASE) or re.search(self.prop_end_nets_regex, line, re.IGNORECASE)): m = re.search(self.prop_end_inst_regex, line, re.IGNORECASE) if m and gpio_idx is not None: name = m.group("instance_name") if current_hier == "": hier_name = name else: hier_name = "{}/{}".format(current_hier, name) gpio_dict[hier_name] = gpio_idx gpio_idx = None in_prop = False elif self.config_ip_pat in line \ and self.config_ignore_pat not in line: m1 = re.search(self.config_regex, line) if m1: key = m1.group("key") value = m1.group("value") if key == "NUM_PORTS": self.concat_cells[last_concat] = int(value) elif key == 'DIN_FROM': gpio_idx = int(value) elif self.is_clk_divisor_line(line): m2 = re.search(self.clk_odiv_regex, key) pl_clk_idx = int(m2.group("pl_idx")) odiv_idx = m2.group("odiv_idx") divisor_name = 'divisor{}'.format(odiv_idx) if pl_clk_idx not in self.pl_clks: raise ValueError("Invalid PL CLK index") self.clock_dict[pl_clk_idx][ divisor_name] = int(value) elif self.is_clk_enable_line(line): m3 = re.search(self.clk_enable_regex, key) pl_clk_idx = int(m3.group("idx")) if pl_clk_idx not in self.pl_clks: raise ValueError("Invalid PL CLK index") self.clock_dict[pl_clk_idx][ 'enable'] = int(value) # Matching address segment elif self.addr_pat in line: m = re.search(addr_regex, line, re.IGNORECASE) if m: for ip_dict0 in hier_dict: for ip_name, ip_type in \ hier_dict[ip_dict0].items(): ip = (ip_dict0 + '/' + ip_name).lstrip('/') if m.group("hier").startswith(ip + '/'): self.ip_dict[ip] = dict() self.ip_dict[ip]['phys_addr'] = \ int(m.group("addr"), 16) self.ip_dict[ip]['addr_range'] = \ int(m.group("range"), 16) self.ip_dict[ip]['type'] = ip_type self.ip_dict[ip]['state'] = None self.ip_dict[ip]['interrupts'] = dict() self.ip_dict[ip]['gpio'] = dict() self.ip_dict[ip]['fullpath'] = ip self.ip_dict[ip]['mem_id'] = m.group('name') # Match hierarchical cell definition elif self.hier_proc_def_pat in line: m = re.search(self.hier_def_regex, line) hier_name = m.group("name") current_hier = hier_name hier_dict[current_hier] = dict() elif self.hier_proc_end_pat == line: current_hier = "" # Match hierarchical cell use/instantiation elif self.hier_use_pat in line: m = re.search(self.hier_use_regex, line) hier_name = m.group("hier_name") inst_name = m.group("instance_name") inst_path = (current_hier + '/' + inst_name).lstrip('/') inst_dict = dict() for path in hier_dict: psplit = path.split('/') if psplit[0] == hier_name: inst_path += path.lstrip(hier_name) inst_dict[inst_path] = deepcopy(hier_dict[path]) hier_dict.update(inst_dict) # Matching IP cells in root design elif self.ip_pat in line: m = re.search(self.ip_regex, line) ip_name = m.group("ip_name") instance_name = m.group("instance_name") if m.group("ip_name") == self.ps_ip_name: self.partial = False self.ps_name = instance_name addr_regex = "create_bd_addr_seg " +\ "-range (?P<range>0[xX][0-9a-fA-F]+) " +\ "-offset (?P<addr>0[xX][0-9a-fA-F]+) " +\ "\[get_bd_addr_spaces " +\ instance_name + "/Data\] " +\ "\[get_bd_addr_segs (?P<hier>.+?)\] " +\ "(?P<name>[A-Za-z0-9_]+)" else: ip_type = ':'.join([m.group(1), m.group(2), m.group(3), m.group(4)]) hier_dict[current_hier][instance_name] = ip_type ip = (current_hier + '/' + instance_name).lstrip('/') if ip_name == "xlconcat": last_concat = ip self.concat_cells[ip] = 2 elif ip_name == "axi_intc": self.intc_names.append(ip) # Matching IP block cells in root design elif self.ip_block_pat in line: m = re.search(self.ip_block_regex, line) instance_name = m.group("instance_name") if m.group('block_name') == '$block_name': name = ip_block_name else: name = m.group('block_name') ip_type = ':'.join(['user', 'ip', name, 'unknown']) hier_dict[current_hier][instance_name] = ip_type # Matching nets elif self.net_pat in line: mpins = re.findall(self.net_regex, line, re.IGNORECASE) new_pins = [(current_hier + "/" + v).lstrip('/') for v in mpins] indexes = {self.pins[p] for p in new_pins if p in self.pins} if len(indexes) == 0: index = len(self.nets) self.nets.append(set()) else: to_merge = [] while len(indexes) > 1: to_merge.append(indexes.pop()) index = indexes.pop() for i in to_merge: self.nets[index] |= self.nets[i] self.nets[index] |= set(new_pins) for p in self.nets[index]: self.pins[p] = index if self.ps_name + "/" + self.irq_pin_name in self.pins: ps_irq_net = self.pins[ self.ps_name + "/" + self.irq_pin_name] self._add_interrupt_pins(ps_irq_net, "", 0) if self.ps_name + "/" + self.gpio_pin_name in self.pins: ps_gpio_net = self.pins[ self.ps_name + "/" + self.gpio_pin_name] self._add_gpio_pins(ps_gpio_net, gpio_dict) self._build_hierarchy_dict() self._assign_interrupts_gpio() self.init_mem_dict() def init_mem_dict(self): """Prepare the memory dictionary For now we will add a single entry for the PS """ from pynq.xlnk import Xlnk self.mem_dict[self.ps_name] = { 'raw_type': None, 'used': 1, 'base_address':0, 'size': Xlnk.cma_mem_size(None), 'type': 'PSDDR', 'streaming': False } def _add_gpio_pins(self, net, gpio_dict): net_pins = self.nets[net] gpio_names = [] for p in net_pins: m = re.match('(.*)/Din', p) if m is not None: gpio_names.append(m.group(1)) for n, i in gpio_dict.items(): if n in gpio_names: output_net = self.pins['{}/Dout'.format(n)] output_pins = self.nets[output_net] self.gpio_dict[n] = {'index': i, 'state': None, 'pins': output_pins} def _add_interrupt_pins(self, net, parent, offset): net_pins = self.nets[net] # Find the next item up the chain for p in net_pins: m = re.match('(.*)/dout', p) if m is not None: name = m.group(1) if name in self.concat_cells: return self._add_concat_pins(name, parent, offset) m = re.match('(.*)/irq', p) if m is not None: name = m.group(1) if name in self.intc_names: self._add_interrupt_pins( self.pins[name + "/intr"], name, 0) self.interrupt_controllers[name] = {'parent': parent, 'index': offset} return offset + 1 for p in net_pins: self.interrupt_pins[p] = {'controller': parent, 'index': offset, 'fullpath': p} return offset + 1 def _add_concat_pins(self, name, parent, offset): num_ports = self.concat_cells[name] for i in range(num_ports): if (name + "/In" + str(i)) in self.pins: net = self.pins[name + "/In" + str(i)] offset = self._add_interrupt_pins(net, parent, offset) else: offset = 1 return offset def _assign_interrupts_gpio(self): for interrupt, val in self.interrupt_pins.items(): block, _, pin = interrupt.rpartition('/') if block in self.ip_dict: self.ip_dict[block]['interrupts'][pin] = val if block in self.hierarchy_dict: self.hierarchy_dict[block]['interrupts'][pin] = val for gpio in self.gpio_dict.values(): for connection in gpio['pins']: ip, _, pin = connection.rpartition('/') if ip in self.ip_dict: self.ip_dict[ip]['gpio'][pin] = gpio elif ip in self.hierarchy_dict: self.hierarchy_dict[ip]['gpio'][pin] = gpio def _build_hierarchy_dict(self): lasthierarchies = {} hierarchies = {k.rpartition('/')[0] for k in self.ip_dict.keys() if k.count('/') > 0} while lasthierarchies != hierarchies: parents = {k.rpartition('/')[0] for k in hierarchies if k.count('/') > 0} lasthierarchies = hierarchies hierarchies.update(parents) self.hierarchy_dict = dict() for hier in hierarchies: self.hierarchy_dict[hier] = { 'ip': dict(), 'hierarchies': dict(), 'interrupts': dict(), 'gpio': dict(), 'memories': dict(), 'fullpath': hier, } for name, val in self.ip_dict.items(): hier, _, ip = name.rpartition('/') if hier: self.hierarchy_dict[hier]['ip'][ip] = val for name, val in self.hierarchy_dict.items(): hier, _, subhier = name.rpartition('/') if hier: self.hierarchy_dict[hier]['hierarchies'][subhier] = val class _TCLUltrascale(_TCLABC): """Intermediate class to extract information from a TCL configuration. This class works for the Zynq Ultrascale devices. The following additional attributes are added to the ABC class. Attributes ---------- clock_dict : dict All the PL clocks that can be controlled by the PS. Key is the index of the clock (e.g., 0 for the first clock); value is a dictionary mapping the divisor values and the enable flag (1 for enabled, and 0 for disabled): {index: {'divisor0' : int, 'divisor1' : int, 'enable' : int}} """ ps_ip_name = "zynq_ultra_ps_e" irq_pin_offset = 0 irq_pin_name = "pl_ps_irq{}".format(irq_pin_offset) gpio_pin_name = "emio_gpio_o" clk_odiv_regex = 'PSU__CRL_APB__PL(?P<pl_idx>.+?)' \ '_REF_CTRL__DIVISOR(?P<odiv_idx>.+?)' clk_enable_regex = 'PSU__FPGA_PL(?P<idx>.+?)_ENABLE' pl_clks = [0, 1, 2, 3] def __init__(self, tcl_name): """Returns an Ultrascale-specific map built from the supplied tcl file Parameters --------- tcl_name : str The tcl filename to parse. This is opened directly so should be fully qualified """ self.clock_dict = dict() for pl_clk in self.pl_clks: self.clock_dict[pl_clk] = dict() self.clock_dict[pl_clk]['enable'] = 0 self.clock_dict[pl_clk]["divisor0"] = 10 self.clock_dict[pl_clk]["divisor1"] = 1 self.clock_dict[0]['enable'] = 1 super().__init__(tcl_name) def is_clk_enable_line(self, line): """Returns True if line contains a declaration to enable a PL clock otherwise False Parameters --------- line : str The string from a line in a tcl file """ return "PSU__FPGA_PL" in line and "ENABLE" in line def is_clk_divisor_line(self, line): """Returns True if line contains a declaration to set a PL clock divisor otherwise False Parameters --------- line : str The string from a line in a tcl file """ return "PSU__CRL_APB__PL" in line and "REF_CTRL__DIVISOR" in line class _TCLZynq(_TCLABC): """Intermediate class to extract information from a TCL configuration. This class works for the Zynq devices. The following additional attributes are added to the ABC class. Attributes ---------- clock_dict : dict All the PL clocks that can be controlled by the PS. Key is the index of the clock (e.g., 0 for the first clock); value is a dictionary mapping the divisor values and the enable flag (1 for enabled, and 0 for disabled): {index: {'divisor0' : int, 'divisor1' : int, 'enable' : int}} """ ps_ip_name = "processing_system7" irq_pin_offset = 0 irq_pin_name = "IRQ_F2P" gpio_pin_name = "GPIO_O" clk_odiv_regex = 'PCW_FCLK(?P<pl_idx>.+?)_PERIPHERAL_DIVISOR' \ '(?P<odiv_idx>[01])$' clk_enable_regex = 'PCW_FPGA_FCLK(?P<idx>.+?)_ENABLE' pl_clks = [0, 1, 2, 3] def __init__(self, tcl_name): """Returns an Ultrascale-specific map built from the supplied tcl file Parameters --------- tcl_name : str The tcl filename to parse. This is opened directly so should be fully qualified """ self.clock_dict = dict() for pl_clk in self.pl_clks: self.clock_dict[pl_clk] = dict() self.clock_dict[pl_clk]['enable'] = 0 self.clock_dict[pl_clk]["divisor0"] = 10 self.clock_dict[pl_clk]["divisor1"] = 1 self.clock_dict[0]['enable'] = 1 super().__init__(tcl_name) def is_clk_enable_line(self, line): """Returns True if line contains a declaration to enable a PL clock otherwise False Parameters --------- line : str The string from a line in a tcl file """ return "FCLK" in line and "ENABLE" in line def is_clk_divisor_line(self, line): """Returns True if line contains a declaration to set a PL clock divisor otherwise False Parameters --------- line : str The string from a line in a tcl file """ return "FCLK" in line and "PERIPHERAL_DIVISOR" in line if CPU_ARCH == ZU_ARCH: TCL = _TCLUltrascale elif CPU_ARCH == ZYNQ_ARCH: TCL = _TCLZynq else: TCL = _TCLABC