aboutsummaryrefslogtreecommitdiff
path: root/kicad/lib/tools/lattice2kipart.py
blob: 3e100742bbd2e87c08e40c5a9e9bac45eff4afa7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#!/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()