365 lines
11 KiB
Python
365 lines
11 KiB
Python
|
#!/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()
|