File: isptcl.py

package info (click to toggle)
prjtrellis 1.4-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 83,000 kB
  • sloc: cpp: 20,813; python: 16,246; sh: 375; makefile: 262; asm: 80; ansic: 58
file content (189 lines) | stat: -rw-r--r-- 7,098 bytes parent folder | download | duplicates (2)
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
"""
Interface between Python fuzzer scripts and Lattice Diamond ispTcl
"""

from collections import defaultdict

import database
import subprocess
import tempfile
from os import path
import re


# Arc whose direction is ambiguous "---"
class AmbiguousArc:
    # I
    def __init__(self, lhs, rhs):
        self.lhs = lhs
        self.rhs = rhs

    def __getitem__(self, idx):
        if idx == 0:
            return self.lhs
        elif idx == 1:
            return self.rhs
        else:
            raise IndexError("AmbiguousArc only connects two nets")

    def __repr__(self):
        return "{} --- {}".format(self.lhs, self.rhs)


def run(commands):
    """Run a list of Tcl commands, returning the output as a string"""
    dtcl_path = path.join(database.get_trellis_root(), "diamond_tcl.sh")
    workdir = tempfile.mkdtemp()
    scriptfile = path.join(workdir, "script.tcl")
    with open(scriptfile, 'w') as f:
        f.write('source $::env(FOUNDRY)/data/tcltool/IspTclDev.tcl\n')
        f.write('source $::env(FOUNDRY)/data/tcltool/IspTclCmd.tcl\n')
        for c in commands:
            f.write(c + '\n')
    result = subprocess.run(["bash", dtcl_path, scriptfile], cwd=workdir).returncode
    assert result == 0, "ispTcl returned non-zero status code {}".format(result)
    outfile = path.join(workdir, 'ispTcl.log')
    with open(outfile, 'r') as f:
        output = f.read()
    # Strip Lattice header
    delimiter = "-" * 80
    output = output[output.rindex(delimiter)+81:].strip()
    # Strip Lattice pleasantry
    pleasantry = "Thank you for using ispTcl."
    output = output.replace(pleasantry, "").strip()
    return output

# All these following commands require a tuple (ncdfile, prffile) containing
# a specimen design for the target device

def run_ncd_prf(desfiles, commands):
    """
    Run a list of Tcl commands after loading given .ncd and .prf files

    desfiles: a tuple (ncdfile, prffile)
    commands: list of Tcl commands to run

    Returns the output from IspTcl, excluding header and pleasantry
    """
    run_cmds = [
        "des_read_ncd {}".format(path.abspath(desfiles[0])),
        "des_read_prf {}".format(path.abspath(desfiles[1]))
    ] + commands
    result = run(run_cmds)
    # Remove output of des_read_x
    is_header = True
    output = ""
    for line in result.split('\n'):
        if line.startswith("Reading preference file"):
            is_header = False
        elif not is_header:
            output += line
            output += "\n"
    output = output.replace("ERROR: Placement is not performanced.", "")
    return output


def get_wires_at_position(desfiles, position):
    """
    Use ispTcl to get a list of wires at a given grid position

    desfiles: a tuple (ncdfile, prffile)
    position: a tuple (row, col)

    Returns a list of tuples
    (name, type, typeid)
    """
    command = ["dev_list_node_at_location -row {} -col {}".format(position[0], position[1])]
    result = run_ncd_prf(desfiles, command)
    wires = []
    for line in result.split('\n'):
        sline = line.strip()
        if sline == "":
            continue
        splitline = re.split('\s+', sline)
        assert len(splitline) >= 3
        wires.append((splitline[2].strip(), splitline[0].strip(), int(splitline[1])))
    return wires


def get_arcs_on_wires(desfiles, wires, drivers_only=False, dir_override=dict()):
    """
    Use ispTcl to get a list of arcs sinking or sourcing a list of wires

    desfiles: a tuple (ncdfile, prffile)
    wires: list of canonical names of the wire
    drivers_only: only include arcs driving the wire in the output
    dir_override: Dictionary that specificies whether a net queried by ispTcl
    is a "sink" or "driver" when ispTcl returns "---" (since ISPTcl always puts
    the queried net on the RHS of an an arc). dir_override is only consulted if
    ispTcl returns "---" for the direction of a given net, and will
    additionally override drivers_only=False for any nets specified as
    "driver". Two additional strings are allowed: "ignore" to ignore "---"
    connections to/from the queried net, and "mark" to return the connection as
    an AmbiguousArc for later processing.

    Returns a map between wire name and a list of arc tuples (source, sink)
    """
    arcmap = {}
    wire_idx = 0

    # We can only process a limited number of nodes at a time, due to a memory leak in the Tcl API :facepalm:
    for i in range(0, len(wires), 10):
        subwires = wires[i:i+10]
        command = []
        for wire in subwires:
            command += ["dev_list_arc_by_node_name -to {} -num 100000".format(wire), 'prj_list']
        result = run_ncd_prf(desfiles, command)
        arcs = []
        for line in result.split('\n'):
            sline = line.strip()
            if sline == "":
                pass
            elif sline.startswith("MyIspProject"):
                arcmap[wires[wire_idx]] = list(arcs)
                wire_idx += 1
                arcs = []
            else:
                splitline = re.split('\s+', sline)
                assert len(splitline) >= 3
                if splitline[1].strip() == "-->":
                    arcs.append((splitline[0].strip(), splitline[2].strip()))
                elif splitline[1].strip() == "<--":
                    if not drivers_only:
                        arcs.append((splitline[2].strip(), splitline[0].strip()))
                elif splitline[1].strip() == "---":
                    if isinstance(dir_override, defaultdict):
                        # get() overrides defaultdict behavior, and a user may
                        # have a valid reason to provide a default such as
                        # ignore.
                        override = dir_override[wires[wire_idx]]
                    else:
                        override = dir_override.get(wires[wire_idx], "")
                    if override:
                        if override == "sink":
                            arcs.append((splitline[0].strip(), splitline[2].strip()))
                        elif override == "driver":
                            arcs.append((splitline[2].strip(), splitline[0].strip()))
                        elif override == "mark":
                            arcs.append(AmbiguousArc(splitline[0].strip(), splitline[2].strip()))
                        elif override == "ignore":
                            pass
                        else:
                            assert False, ("invalid override for wire {}".
                                            format(wires[wire_idx]))
                    else:
                        assert False, ("'---' found in ispTcl output, and no netdir_override"
                                       " was given for {wire}. Full line:\n{line}".
                                       format(wire=wires[wire_idx], line=line))
                else:
                    print (splitline)
                    assert False, "invalid output from Tcl command `dev_list_arcs`"
    return arcmap


def main():
    print(run([]))


if __name__ == "__main__":
    main()