#!/usr/bin/env python3 """ This script parses given interface pin-mapping xml file, stores the pin-mapping information w.r.t. its location in the device. It also generates a template csv file for the end-user of the eFPGA device. User can modify the template csv file and specify the user-defined pin names to the ports defined in the template csv file. """ import argparse import csv import sys from collections import namedtuple import lxml.etree as ET # ============================================================================= class PinMappingData(object): """ Pin mapping data for IO ports in an eFPGA device. port_name - IO port name mapped_pin - User-defined pin name mapped to the given port_name x - x coordinate corresponding to column number y - y coordinate corresponding to row number z - z coordinate corresponding to pin index at current x,y location """ def __init__(self, port_name, mapped_pin, x, y, z): self.port_name = port_name self.mapped_pin = mapped_pin self.x = x self.y = y self.z = z def __str__(self): return "{Port_name: '%s' mapped_pin: '%s' x: '%s' y: '%s' z: '%s'}" % ( self.port_name, self.mapped_pin, self.x, self.y, self.z ) def __repr__(self): return "{Port_name: '%s' mapped_pin: '%s' x: '%s' y: '%s' z: '%s'}" % ( self.port_name, self.mapped_pin, self.x, self.y, self.z ) """ Device properties present in the pin-mapping xml name - Device name family - Device family name width - Device width aka number of cells in a row heigth - Device height aka number of cells in a column z - Number of cells per row/col """ DeviceData = namedtuple("DeviceData", "name family width height z") # ============================================================================= def parse_io(xml_io, port_map, orientation, width, height, z): ''' Parses IO section of xml file ''' assert xml_io is not None pins = {} io_row = "" io_col = "" if orientation in ("TOP", "BOTTOM"): io_row = xml_io.get("y") if io_row is None: if orientation == "TOP": io_row = str(int(height) - 1) elif orientation == "BOTTOM": io_row = "0" elif orientation in ("LEFT", "RIGHT"): io_col = xml_io.get("x") if io_col is None: if orientation == "LEFT": io_col = "0" elif orientation == "RIGHT": io_col = str(int(width) - 1) for xml_cell in xml_io.findall("CELL"): port_name = xml_cell.get("port_name") mapped_name = xml_cell.get("mapped_name") startx = xml_cell.get("startx") starty = xml_cell.get("starty") endx = xml_cell.get("endx") endy = xml_cell.get("endy") # define properties for scalar pins scalar_mapped_pins = vec_to_scalar(mapped_name) i = 0 if startx is not None and endx is not None: curr_startx = int(startx) curr_endx = int(endx) y = io_row if curr_startx < curr_endx: for x in range(curr_startx, curr_endx + 1): for j in range(0, int(z)): pins[x, y, j] = PinMappingData( port_name=port_name, mapped_pin=scalar_mapped_pins[i], x=x, y=y, z=j ) port_map[scalar_mapped_pins[i]] = pins[x, y, j] i += 1 else: for x in range(curr_startx, curr_endx - 1, -1): for j in range(0, int(z)): pins[x, y, j] = PinMappingData( port_name=port_name, mapped_pin=scalar_mapped_pins[i], x=x, y=y, z=j ) port_map[scalar_mapped_pins[i]] = pins[x, y, j] i += 1 elif starty is not None and endy is not None: curr_starty = int(starty) curr_endy = int(endy) x = io_col if curr_starty < curr_endy: for y in range(curr_starty, curr_endy + 1): for j in range(0, int(z)): pins[x, y, j] = PinMappingData( port_name=port_name, mapped_pin=scalar_mapped_pins[i], x=x, y=y, z=j ) port_map[scalar_mapped_pins[i]] = pins[x, y, j] i += 1 else: for y in range(curr_starty, curr_endy - 1, -1): for j in range(0, int(z)): pins[x, y, j] = PinMappingData( port_name=port_name, mapped_pin=scalar_mapped_pins[i], x=x, y=y, z=j ) port_map[scalar_mapped_pins[i]] = pins[x, y, j] i += 1 return pins, port_map # ============================================================================= def vec_to_scalar(port_name): ''' Converts given bus port into its scalar ports ''' scalar_ports = [] if port_name is not None and ':' in port_name: open_brace = port_name.find('[') close_brace = port_name.find(']') if open_brace == -1 or close_brace == -1: print( 'Invalid portname "{}" specified. Bus ports should contain [ ] to specify range' .format(port_name), file=sys.stderr ) sys.exit(1) bus = port_name[open_brace + 1:close_brace] lsb = int(bus[:bus.find(':')]) msb = int(bus[bus.find(':') + 1:]) if lsb > msb: for i in range(lsb, msb - 1, -1): curr_port_name = port_name[:open_brace] + '[' + str(i) + ']' scalar_ports.append(curr_port_name) else: for i in range(lsb, msb + 1): curr_port_name = port_name[:open_brace] + '[' + str(i) + ']' scalar_ports.append(curr_port_name) else: scalar_ports.append(port_name) return scalar_ports # ============================================================================= def parse_io_cells(xml_root): """ Parses the "IO" section of the pinmapfile. Returns a dict indexed by IO cell names which contains cell types and their locations in the device grid. """ cells = {} port_map = {} width = xml_root.get("width"), height = xml_root.get("height"), io_per_cell = xml_root.get("z") # Get the "IO" section xml_io = xml_root.find("IO") if xml_io is None: print("ERROR: No mandatory 'IO' section defined in 'DEVICE' section") sys.exit(1) xml_top_io = xml_io.find("TOP_IO") if xml_top_io is not None: currcells, port_map = parse_io( xml_top_io, port_map, "TOP", width, height, io_per_cell ) cells["TOP"] = currcells xml_bottom_io = xml_io.find("BOTTOM_IO") if xml_bottom_io is not None: currcells, port_map = parse_io( xml_bottom_io, port_map, "BOTTOM", width, height, io_per_cell ) cells["BOTTOM"] = currcells xml_left_io = xml_io.find("LEFT_IO") if xml_left_io is not None: currcells, port_map = parse_io( xml_left_io, port_map, "LEFT", width, height, io_per_cell ) cells["LEFT"] = currcells xml_right_io = xml_io.find("RIGHT_IO") if xml_right_io is not None: currcells, port_map = parse_io( xml_right_io, port_map, "RIGHT", width, height, io_per_cell ) cells["RIGHT"] = currcells return cells, port_map # ============================================================================ def read_pinmapfile_data(pinmapfile): """ Loads and parses a pinmap file """ # Read and parse the XML archfile parser = ET.XMLParser(resolve_entities=False, strip_cdata=False) xml_tree = ET.parse(pinmapfile, parser) xml_root = xml_tree.getroot() if xml_root.get("name") is None: print( "ERROR: No mandatory attribute 'name' specified in 'DEVICE' section" ) sys.exit(1) if xml_root.get("family") is None: print( "ERROR: No mandatory attribute 'family' specified in 'DEVICE' section" ) sys.exit(1) if xml_root.get("width") is None: print( "ERROR: No mandatory attribute 'width' specified in 'DEVICE' section" ) sys.exit(1) if xml_root.get("height") is None: print( "ERROR: No mandatory attribute 'height' specified in 'DEVICE' section" ) sys.exit(1) if xml_root.get("z") is None: print( "ERROR: No mandatory attribute 'z' specified in 'DEVICE' section" ) sys.exit(1) # Parse IO cells io_cells, port_map = parse_io_cells(xml_root) return io_cells, port_map # ============================================================================= def generate_pinmap_csv(pinmap_csv_file, io_cells): ''' Generates pinmap csv file ''' with open(pinmap_csv_file, "w", newline='') as csvfile: fieldnames = [ 'orientation', 'row', 'col', 'pin_num_in_cell', 'port_name', 'mapped_pin', 'GPIO_type', 'Associated Clock', 'Clock Edge' ] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for orientation, pin_map in io_cells.items(): for pin_loc, pin_obj in pin_map.items(): writer.writerow( { 'orientation': orientation, 'row': str(pin_obj.y), 'col': str(pin_obj.x), 'pin_num_in_cell': str(pin_obj.z), 'port_name': pin_obj.mapped_pin, } ) # ============================================================================= def main(): ''' Processes interface mapping xml file and generates template csv file ''' # Parse arguments parser = argparse.ArgumentParser( description='Process interface mapping xml file to generate csv file.' ) parser.add_argument( "--pinmapfile", "-p", "-P", type=str, required=True, help="Input pin-mapping XML file" ) parser.add_argument( "--csv_file", "-c", "-C", type=str, default="template_pinmap.csv", help="Output template pinmap CSV file" ) args = parser.parse_args() # Load all the necessary data from the pinmapfile io_cells, port_map = read_pinmapfile_data(args.pinmapfile) # Generate the pinmap CSV generate_pinmap_csv(args.csv_file, io_cells) if __name__ == "__main__": main()