blob: 977f2954ec260ce5bb826c560cdb7e1a0fe12838 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2022 F4PGA Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
"""
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()