#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Convert a Lattice ECP5 pinout CSV into a kipart input CSV. Usage: lattice2kipart.py PINOUT.csv PACKAGE PARTNAME FOOTPRINT LUTS > part.csv kipart -b -o out.kicad_sym part.csv One unit per I/O bank, one unit for configuration/JTAG, one unit for core/aux power and ground. Dual functions are appended to the pin name (e.g. PL32A/PCLKT7_1) so clock and config pins are visible in the schematic. """ import csv import os import re import sys IO_RE = re.compile(r"P[TBLR]\d+[A-D]$") # Dedicated pins: name -> (kicad name, type, side, unit) DEDICATED = { "PROGRAMN": ("~{PROGRAMN}", "input", "left", "Config"), "INITN": ("~{INITN}", "open_collector", "left", "Config"), "DONE": ("DONE", "open_collector", "left", "Config"), "CCLK": ("CCLK", "bidirectional", "left", "Config"), "CFG_0": ("CFG_0", "input", "left", "Config"), "CFG_1": ("CFG_1", "input", "left", "Config"), "CFG_2": ("CFG_2", "input", "left", "Config"), "TCK": ("TCK", "input", "right", "Config"), "TMS": ("TMS", "input", "right", "Config"), "TDI": ("TDI", "input", "right", "Config"), "TDO": ("TDO", "output", "right", "Config"), "VCC": ("VCC", "power_in", "top", "Power"), "VCCAUX": ("VCCAUX", "power_in", "top", "Power"), "GND": ("GND", "power_in", "bottom", "Power"), } def main(): src, package, part, footprint, luts = sys.argv[1:6] rows = [r for r in csv.reader(open(src, newline="")) if r and any(c.strip() for c in r) and not r[0].startswith("#")] header = rows[0] pins = [dict(zip(header, r)) for r in rows[1:]] if package not in header: sys.exit(f"package {package} not in {header}") pins = [p for p in pins if p[package].strip() not in ("", "-")] # Split each bank's IOs: first half left side, second half right side. banks = {} for p in pins: if IO_RE.match(p["Pin/Ball Function"]): banks.setdefault(p["Bank"], []).append(p) side_of = {} for b, lst in banks.items(): half = (len(lst) + 1) // 2 for i, p in enumerate(lst): side_of[id(p)] = "left" if i < half else "right" out = csv.writer(sys.stdout) out.writerow([part]) out.writerow(["Footprint:", footprint]) out.writerow(["Datasheet:", "https://www.latticesemi.com/view_document?document_id=50461"]) out.writerow(["Description:", f"ECP5 FPGA, {luts} LUTs, 1.1V core, {package}"]) out.writerow(["Source*:", f"Lattice pinout CSV {os.path.basename(src)}, package {package}"]) out.writerow(["Keywords:", "FPGA programmable logic Lattice ECP5"]) out.writerow(["Filters:", "BGA*256*14.0x14.0mm*P0.8mm*"]) out.writerow(["pin", "name", "type", "side", "unit"]) rows_out = [] seen = set() for p in pins: ball = p[package].strip() if ball in seen: sys.exit(f"duplicate ball {ball}") seen.add(ball) func = p["Pin/Ball Function"].strip() dual = p["Dual Function"].strip() bank = p["Bank"].strip() if IO_RE.match(func): name = func if dual in ("", "-") else f"{func}/{dual}" rows_out.append([ball, name, "bidirectional", side_of[id(p)], f"Bank{bank}"]) elif func.startswith("VCCIO"): rows_out.append([ball, func, "power_in", "top", f"Bank{bank}"]) elif func in DEDICATED: name, typ, side, unit = DEDICATED[func] if func == "CCLK" and dual not in ("", "-"): name = f"CCLK/{dual}" rows_out.append([ball, name, typ, side, unit]) else: sys.exit(f"unhandled pin {func} ({ball})") # kipart numbers units in order of first appearance: banks ascending, # then Config, then Power. Pin order inside a unit stays as in the CSV. def unit_key(unit): if unit.startswith("Bank"): return (0, int(unit[4:])) return (1, ["Config", "Power"].index(unit)) rows_out.sort(key=lambda r: unit_key(r[4])) out.writerows(rows_out) if __name__ == "__main__": main()