mnt.designer 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mnt/designer/app.py +1701 -0
- mnt/designer/static/css/styles.css +243 -0
- mnt/designer/static/favicon.ico +0 -0
- mnt/designer/static/gates/AND_DIN.svg +8 -0
- mnt/designer/static/gates/BUFC.svg +11 -0
- mnt/designer/static/gates/BUFK.svg +11 -0
- mnt/designer/static/gates/BUF_DIN.svg +7 -0
- mnt/designer/static/gates/INV_DIN.svg +8 -0
- mnt/designer/static/gates/NOR_DIN.svg +9 -0
- mnt/designer/static/gates/OR_DIN.svg +8 -0
- mnt/designer/static/gates/XNOR_DIN.svg +12 -0
- mnt/designer/static/gates/XOR_DIN.svg +11 -0
- mnt/designer/static/js/script.js +2351 -0
- mnt/designer/static/mnt_dark.svg +173 -0
- mnt/designer/static/tum_logo.svg +12 -0
- mnt/designer/templates/index.html +1285 -0
- mnt_designer-0.2.0.dist-info/METADATA +155 -0
- mnt_designer-0.2.0.dist-info/RECORD +22 -0
- mnt_designer-0.2.0.dist-info/WHEEL +5 -0
- mnt_designer-0.2.0.dist-info/entry_points.txt +3 -0
- mnt_designer-0.2.0.dist-info/licenses/LICENSE +21 -0
- mnt_designer-0.2.0.dist-info/top_level.txt +1 -0
mnt/designer/app.py
ADDED
|
@@ -0,0 +1,1701 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import io
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import secrets
|
|
7
|
+
import struct
|
|
8
|
+
import tempfile
|
|
9
|
+
import uuid
|
|
10
|
+
import webbrowser
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
from contextlib import redirect_stdout
|
|
13
|
+
from importlib.metadata import version
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from flask import Flask, cli, jsonify, render_template, request, send_file, session
|
|
17
|
+
from werkzeug.exceptions import RequestEntityTooLarge
|
|
18
|
+
|
|
19
|
+
from mnt.pyfiction import (
|
|
20
|
+
a_star,
|
|
21
|
+
apply_bestagon_library,
|
|
22
|
+
apply_qca_one_library,
|
|
23
|
+
cartesian_gate_layout,
|
|
24
|
+
cartesian_obstruction_layout,
|
|
25
|
+
color_mode,
|
|
26
|
+
eq_type,
|
|
27
|
+
equivalence_checking,
|
|
28
|
+
equivalence_checking_stats,
|
|
29
|
+
gate_level_drvs,
|
|
30
|
+
gold_cost_objective,
|
|
31
|
+
gold_effort_mode,
|
|
32
|
+
graph_oriented_layout_design,
|
|
33
|
+
graph_oriented_layout_design_params,
|
|
34
|
+
hexagonalization,
|
|
35
|
+
orthogonal,
|
|
36
|
+
post_layout_optimization,
|
|
37
|
+
post_layout_optimization_params,
|
|
38
|
+
read_cartesian_fgl_layout,
|
|
39
|
+
read_technology_network,
|
|
40
|
+
route_path,
|
|
41
|
+
write_dot_layout,
|
|
42
|
+
write_fgl_layout,
|
|
43
|
+
write_qca_layout_svg,
|
|
44
|
+
write_qca_layout_svg_params,
|
|
45
|
+
write_sidb_layout_svg,
|
|
46
|
+
write_sidb_layout_svg_params,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
from mnt.pyfiction import exact_cartesian, exact_params
|
|
51
|
+
except (ImportError, AttributeError):
|
|
52
|
+
exact_params = None
|
|
53
|
+
exact_cartesian = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
app = Flask(__name__)
|
|
57
|
+
app.secret_key = os.environ.get("MNT_DESIGNER_SECRET_KEY") or secrets.token_hex(32)
|
|
58
|
+
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
|
|
59
|
+
app.config["MAX_CONTENT_LENGTH"] = 5 * 1024 * 1024 # Entire request, including multipart overhead.
|
|
60
|
+
|
|
61
|
+
# Layouts and networks are process-local; use a single worker for hosted instances.
|
|
62
|
+
layouts = {}
|
|
63
|
+
|
|
64
|
+
# In-memory storage for user networks
|
|
65
|
+
networks = {}
|
|
66
|
+
|
|
67
|
+
# In-memory storage for user verilog
|
|
68
|
+
verilogs = {}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.errorhandler(RequestEntityTooLarge)
|
|
72
|
+
def request_too_large(_error):
|
|
73
|
+
return jsonify({"success": False, "error": "Request exceeds the upload size limit."}), 413
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _internal_error_response():
|
|
77
|
+
app.logger.exception("Unhandled request error")
|
|
78
|
+
return jsonify({"success": False, "error": "An internal error occurred."})
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@app.route("/")
|
|
82
|
+
def index():
|
|
83
|
+
# Assign a unique session ID if not already present
|
|
84
|
+
if "session_id" not in session:
|
|
85
|
+
session["session_id"] = str(uuid.uuid4())
|
|
86
|
+
return render_template("index.html")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@app.route("/create_layout", methods=["POST"])
|
|
90
|
+
def create_layout():
|
|
91
|
+
try:
|
|
92
|
+
data = request.json
|
|
93
|
+
x = int(data.get("x")) - 1
|
|
94
|
+
y = int(data.get("y")) - 1
|
|
95
|
+
z = 1 # Default Z value
|
|
96
|
+
if x < 0 or y < 0:
|
|
97
|
+
return jsonify({"success": False, "error": "Layout dimensions must be positive."}), 400
|
|
98
|
+
|
|
99
|
+
session_id = session["session_id"]
|
|
100
|
+
layout = layouts.get(session_id)
|
|
101
|
+
|
|
102
|
+
if layout:
|
|
103
|
+
_, maximum = layout.bounding_box_2d()
|
|
104
|
+
if maximum.x > x or maximum.y > y:
|
|
105
|
+
return jsonify({"success": False, "error": "Layout dimensions would hide existing gates."}), 400
|
|
106
|
+
|
|
107
|
+
if not layout:
|
|
108
|
+
# Create a new layout if one doesn't exist
|
|
109
|
+
layout = cartesian_obstruction_layout(cartesian_gate_layout((0, 0, 0), "2DDWave", "Layout"))
|
|
110
|
+
layouts[session_id] = layout
|
|
111
|
+
|
|
112
|
+
# Resize the existing layout
|
|
113
|
+
layout.resize((x, y, z))
|
|
114
|
+
return jsonify({"success": True})
|
|
115
|
+
except RequestEntityTooLarge:
|
|
116
|
+
raise
|
|
117
|
+
except Exception:
|
|
118
|
+
return _internal_error_response()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@app.route("/reset_layout", methods=["POST"])
|
|
122
|
+
def reset_layout():
|
|
123
|
+
try:
|
|
124
|
+
data = request.json
|
|
125
|
+
x = int(data.get("x")) - 1
|
|
126
|
+
y = int(data.get("y")) - 1
|
|
127
|
+
z = 1 # Default Z value
|
|
128
|
+
if x < 0 or y < 0:
|
|
129
|
+
return jsonify({"success": False, "error": "Layout dimensions must be positive."}), 400
|
|
130
|
+
layout = cartesian_obstruction_layout(cartesian_gate_layout((x, y, z), "2DDWave", "Layout"))
|
|
131
|
+
|
|
132
|
+
session_id = session["session_id"]
|
|
133
|
+
layouts[session_id] = layout
|
|
134
|
+
|
|
135
|
+
return jsonify({"success": True})
|
|
136
|
+
except RequestEntityTooLarge:
|
|
137
|
+
raise
|
|
138
|
+
except Exception:
|
|
139
|
+
return _internal_error_response()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@app.route("/reset_editor", methods=["POST"])
|
|
143
|
+
def reset_editor():
|
|
144
|
+
try:
|
|
145
|
+
session_id = session.get("session_id")
|
|
146
|
+
if not session_id:
|
|
147
|
+
return jsonify({"success": False, "error": "Session ID not found."}), 400
|
|
148
|
+
|
|
149
|
+
# Define the default Verilog code
|
|
150
|
+
default_verilog_code = ""
|
|
151
|
+
|
|
152
|
+
# Reset the editor's code to the default
|
|
153
|
+
verilogs[session_id] = default_verilog_code
|
|
154
|
+
networks[session_id] = None
|
|
155
|
+
|
|
156
|
+
return jsonify({"success": True, "code": default_verilog_code}), 200
|
|
157
|
+
|
|
158
|
+
except Exception:
|
|
159
|
+
return _internal_error_response(), 500
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _gate_layer(layout, x, y):
|
|
163
|
+
# The editor projects both native layers onto one tile; an isolated upper wire is still editable.
|
|
164
|
+
return 1 if layout.z() > 0 and layout.is_empty_tile((x, y, 0)) else 0
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@app.route("/place_gate", methods=["POST"])
|
|
168
|
+
def place_gate():
|
|
169
|
+
try:
|
|
170
|
+
data = request.json
|
|
171
|
+
x = int(data["x"])
|
|
172
|
+
y = int(data["y"])
|
|
173
|
+
gate_type = data["gate_type"]
|
|
174
|
+
params = data["params"]
|
|
175
|
+
update_first = False
|
|
176
|
+
update_second = False
|
|
177
|
+
|
|
178
|
+
session_id = session["session_id"]
|
|
179
|
+
layout = layouts.get(session_id)
|
|
180
|
+
if not layout:
|
|
181
|
+
return jsonify({"success": False, "error": "Layout not found."})
|
|
182
|
+
|
|
183
|
+
node = layout.get_node((x, y, _gate_layer(layout, x, y)))
|
|
184
|
+
if node != 0:
|
|
185
|
+
return jsonify({"success": False, "error": "Tile already has a gate."})
|
|
186
|
+
|
|
187
|
+
if gate_type in ["bufc", "bufk"] and layout.z() == 0:
|
|
188
|
+
layout.resize((layout.x(), layout.y(), 1))
|
|
189
|
+
|
|
190
|
+
# Enforce incoming signal constraints
|
|
191
|
+
if gate_type == "pi":
|
|
192
|
+
if params:
|
|
193
|
+
return jsonify({"success": False, "error": "PI gate cannot have inputs."})
|
|
194
|
+
layout.create_pi("", (x, y))
|
|
195
|
+
elif gate_type in ["buf", "inv", "po"]:
|
|
196
|
+
if "first" not in params or "second" in params:
|
|
197
|
+
return jsonify(
|
|
198
|
+
{
|
|
199
|
+
"success": False,
|
|
200
|
+
"error": f"{gate_type.upper()} gate requires exactly one input.",
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
source_x = int(params["first"]["position"]["x"])
|
|
204
|
+
source_y = int(params["first"]["position"]["y"])
|
|
205
|
+
source_z = _gate_layer(layout, source_x, source_y)
|
|
206
|
+
source_gate_type = params["first"]["gate_type"]
|
|
207
|
+
|
|
208
|
+
if source_gate_type == "bufc":
|
|
209
|
+
if source_x < x:
|
|
210
|
+
if layout.has_southern_outgoing_signal((source_x, source_y, 0)):
|
|
211
|
+
source_z = 1
|
|
212
|
+
elif layout.has_southern_outgoing_signal((source_x, source_y, 1)):
|
|
213
|
+
source_z = 0
|
|
214
|
+
else:
|
|
215
|
+
if layout.has_northern_incoming_signal((source_x, source_y, 0)):
|
|
216
|
+
source_z = 1
|
|
217
|
+
elif layout.has_northern_incoming_signal((source_x, source_y, 1)):
|
|
218
|
+
source_z = 0
|
|
219
|
+
else:
|
|
220
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
221
|
+
elif source_y < y:
|
|
222
|
+
if layout.has_eastern_outgoing_signal((source_x, source_y, 0)):
|
|
223
|
+
source_z = 1
|
|
224
|
+
elif layout.has_eastern_outgoing_signal((source_x, source_y, 1)):
|
|
225
|
+
source_z = 0
|
|
226
|
+
else:
|
|
227
|
+
if layout.has_northern_incoming_signal((source_x, source_y, 0)):
|
|
228
|
+
source_z = 0
|
|
229
|
+
elif layout.has_northern_incoming_signal((source_x, source_y, 1)):
|
|
230
|
+
source_z = 1
|
|
231
|
+
else:
|
|
232
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
233
|
+
else:
|
|
234
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
235
|
+
|
|
236
|
+
if source_gate_type == "bufk":
|
|
237
|
+
if source_x < x:
|
|
238
|
+
if layout.has_southern_outgoing_signal((source_x, source_y, 0)):
|
|
239
|
+
source_z = 1
|
|
240
|
+
elif layout.has_southern_outgoing_signal((source_x, source_y, 1)):
|
|
241
|
+
source_z = 0
|
|
242
|
+
else:
|
|
243
|
+
if layout.has_northern_incoming_signal((source_x, source_y, 0)):
|
|
244
|
+
source_z = 0
|
|
245
|
+
elif layout.has_northern_incoming_signal((source_x, source_y, 1)):
|
|
246
|
+
source_z = 1
|
|
247
|
+
else:
|
|
248
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
249
|
+
elif source_y < y:
|
|
250
|
+
if layout.has_eastern_outgoing_signal((source_x, source_y, 0)):
|
|
251
|
+
source_z = 1
|
|
252
|
+
elif layout.has_eastern_outgoing_signal((source_x, source_y, 1)):
|
|
253
|
+
source_z = 0
|
|
254
|
+
else:
|
|
255
|
+
if layout.has_northern_incoming_signal((source_x, source_y, 0)):
|
|
256
|
+
source_z = 1
|
|
257
|
+
elif layout.has_northern_incoming_signal((source_x, source_y, 1)):
|
|
258
|
+
source_z = 0
|
|
259
|
+
else:
|
|
260
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
261
|
+
else:
|
|
262
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
263
|
+
|
|
264
|
+
source_node = layout.get_node((source_x, source_y, source_z))
|
|
265
|
+
if not source_node:
|
|
266
|
+
return jsonify({"success": False, "error": "Source gate not found."})
|
|
267
|
+
|
|
268
|
+
# Check if the gate already has inputs
|
|
269
|
+
existing_fanins = layout.fanins((x, y))
|
|
270
|
+
if existing_fanins:
|
|
271
|
+
return jsonify(
|
|
272
|
+
{
|
|
273
|
+
"success": False,
|
|
274
|
+
"error": f"Gate at ({x}, {y}) cannot have more than 1 input.",
|
|
275
|
+
}
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Determine allowed number of fanouts
|
|
279
|
+
existing_fanouts = layout.fanouts((source_x, source_y, source_z))
|
|
280
|
+
num_fanouts = len(existing_fanouts)
|
|
281
|
+
|
|
282
|
+
if layout.is_po(source_node):
|
|
283
|
+
max_fanouts = 0
|
|
284
|
+
elif layout.is_wire(source_node) and not source_z == 1:
|
|
285
|
+
max_fanouts = 2
|
|
286
|
+
else:
|
|
287
|
+
max_fanouts = 1
|
|
288
|
+
|
|
289
|
+
if num_fanouts >= max_fanouts:
|
|
290
|
+
return jsonify(
|
|
291
|
+
{
|
|
292
|
+
"success": False,
|
|
293
|
+
"error": f"Gate at ({source_x}, {source_y}, {source_z}) cannot have more than {max_fanouts} outgoing connections.",
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
if gate_type == "po":
|
|
298
|
+
layout.create_po(layout.make_signal(source_node), "", (x, y))
|
|
299
|
+
elif gate_type == "inv":
|
|
300
|
+
layout.create_not(layout.make_signal(source_node), (x, y))
|
|
301
|
+
elif gate_type == "buf":
|
|
302
|
+
layout.create_buf(layout.make_signal(source_node), (x, y))
|
|
303
|
+
if layout.fanout_size(source_node) == 2:
|
|
304
|
+
update_first = True
|
|
305
|
+
elif gate_type in ["and", "or", "nor", "xor", "xnor", "bufc", "bufk"]:
|
|
306
|
+
if "first" not in params or "second" not in params:
|
|
307
|
+
return jsonify(
|
|
308
|
+
{
|
|
309
|
+
"success": False,
|
|
310
|
+
"error": f"{gate_type.upper()} gate requires exactly two inputs.",
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
first_x = int(params["first"]["position"]["x"])
|
|
314
|
+
first_y = int(params["first"]["position"]["y"])
|
|
315
|
+
first_z = _gate_layer(layout, first_x, first_y)
|
|
316
|
+
first_source_gate_type = params["first"]["gate_type"]
|
|
317
|
+
if first_source_gate_type == "bufc":
|
|
318
|
+
if first_x < x:
|
|
319
|
+
if layout.has_southern_outgoing_signal((first_x, first_y, 0)):
|
|
320
|
+
first_z = 1
|
|
321
|
+
elif layout.has_southern_outgoing_signal((first_x, first_y, 1)):
|
|
322
|
+
first_z = 0
|
|
323
|
+
else:
|
|
324
|
+
if layout.has_northern_incoming_signal((first_x, first_y, 0)):
|
|
325
|
+
first_z = 1
|
|
326
|
+
elif layout.has_northern_incoming_signal((first_x, first_y, 1)):
|
|
327
|
+
first_z = 0
|
|
328
|
+
else:
|
|
329
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
330
|
+
elif first_y < y:
|
|
331
|
+
if layout.has_eastern_outgoing_signal((first_x, first_y, 0)):
|
|
332
|
+
first_z = 1
|
|
333
|
+
elif layout.has_eastern_outgoing_signal((first_x, first_y, 1)):
|
|
334
|
+
first_z = 0
|
|
335
|
+
else:
|
|
336
|
+
if layout.has_northern_incoming_signal((first_x, first_y, 0)):
|
|
337
|
+
first_z = 0
|
|
338
|
+
elif layout.has_northern_incoming_signal((first_x, first_y, 1)):
|
|
339
|
+
first_z = 1
|
|
340
|
+
else:
|
|
341
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
342
|
+
else:
|
|
343
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
344
|
+
|
|
345
|
+
if first_source_gate_type == "bufk":
|
|
346
|
+
if first_x < x:
|
|
347
|
+
if layout.has_southern_outgoing_signal((first_x, first_y, 0)):
|
|
348
|
+
first_z = 1
|
|
349
|
+
elif layout.has_southern_outgoing_signal((first_x, first_y, 1)):
|
|
350
|
+
first_z = 0
|
|
351
|
+
else:
|
|
352
|
+
if layout.has_northern_incoming_signal((first_x, first_y, 0)):
|
|
353
|
+
first_z = 0
|
|
354
|
+
elif layout.has_northern_incoming_signal((first_x, first_y, 1)):
|
|
355
|
+
first_z = 1
|
|
356
|
+
else:
|
|
357
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
358
|
+
elif first_y < y:
|
|
359
|
+
if layout.has_eastern_outgoing_signal((first_x, first_y, 0)):
|
|
360
|
+
first_z = 1
|
|
361
|
+
elif layout.has_eastern_outgoing_signal((first_x, first_y, 1)):
|
|
362
|
+
first_z = 0
|
|
363
|
+
else:
|
|
364
|
+
if layout.has_northern_incoming_signal((first_x, first_y, 0)):
|
|
365
|
+
first_z = 1
|
|
366
|
+
elif layout.has_northern_incoming_signal((first_x, first_y, 1)):
|
|
367
|
+
first_z = 0
|
|
368
|
+
else:
|
|
369
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
370
|
+
else:
|
|
371
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
372
|
+
second_x = int(params["second"]["position"]["x"])
|
|
373
|
+
second_y = int(params["second"]["position"]["y"])
|
|
374
|
+
second_z = _gate_layer(layout, second_x, second_y)
|
|
375
|
+
second_source_gate_type = params["second"]["gate_type"]
|
|
376
|
+
if second_source_gate_type == "bufc":
|
|
377
|
+
if second_x < x:
|
|
378
|
+
if layout.has_southern_outgoing_signal((second_x, second_y, 0)):
|
|
379
|
+
second_z = 1
|
|
380
|
+
elif layout.has_southern_outgoing_signal((second_x, second_y, 1)):
|
|
381
|
+
second_z = 0
|
|
382
|
+
else:
|
|
383
|
+
if layout.has_northern_incoming_signal((second_x, second_y, 0)):
|
|
384
|
+
second_z = 1
|
|
385
|
+
elif layout.has_northern_incoming_signal((second_x, second_y, 1)):
|
|
386
|
+
second_z = 0
|
|
387
|
+
else:
|
|
388
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
389
|
+
elif second_y < y:
|
|
390
|
+
if layout.has_eastern_outgoing_signal((second_x, second_y, 0)):
|
|
391
|
+
second_z = 1
|
|
392
|
+
elif layout.has_eastern_outgoing_signal((second_x, second_y, 1)):
|
|
393
|
+
second_z = 0
|
|
394
|
+
else:
|
|
395
|
+
if layout.has_northern_incoming_signal((second_x, second_y, 0)):
|
|
396
|
+
second_z = 0
|
|
397
|
+
elif layout.has_northern_incoming_signal((second_x, second_y, 1)):
|
|
398
|
+
second_z = 1
|
|
399
|
+
else:
|
|
400
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
401
|
+
else:
|
|
402
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
403
|
+
|
|
404
|
+
if second_source_gate_type == "bufk":
|
|
405
|
+
if second_x < x:
|
|
406
|
+
if layout.has_southern_outgoing_signal((second_x, second_y, 0)):
|
|
407
|
+
second_z = 1
|
|
408
|
+
elif layout.has_southern_outgoing_signal((second_x, second_y, 1)):
|
|
409
|
+
second_z = 0
|
|
410
|
+
else:
|
|
411
|
+
if layout.has_northern_incoming_signal((second_x, second_y, 0)):
|
|
412
|
+
second_z = 0
|
|
413
|
+
elif layout.has_northern_incoming_signal((second_x, second_y, 1)):
|
|
414
|
+
second_z = 1
|
|
415
|
+
else:
|
|
416
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
417
|
+
elif second_y < y:
|
|
418
|
+
if layout.has_eastern_outgoing_signal((second_x, second_y, 0)):
|
|
419
|
+
second_z = 1
|
|
420
|
+
elif layout.has_eastern_outgoing_signal((second_x, second_y, 1)):
|
|
421
|
+
second_z = 0
|
|
422
|
+
else:
|
|
423
|
+
if layout.has_northern_incoming_signal((second_x, second_y, 0)):
|
|
424
|
+
second_z = 1
|
|
425
|
+
elif layout.has_northern_incoming_signal((second_x, second_y, 1)):
|
|
426
|
+
second_z = 0
|
|
427
|
+
else:
|
|
428
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
429
|
+
else:
|
|
430
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
431
|
+
first_node = layout.get_node((first_x, first_y, first_z))
|
|
432
|
+
second_node = layout.get_node((second_x, second_y, second_z))
|
|
433
|
+
if not first_node or not second_node:
|
|
434
|
+
return jsonify({"success": False, "error": "One or both source gates not found."})
|
|
435
|
+
|
|
436
|
+
# Check if the gate already has inputs
|
|
437
|
+
existing_fanins = layout.fanins((x, y))
|
|
438
|
+
if len(existing_fanins) >= 2:
|
|
439
|
+
return jsonify(
|
|
440
|
+
{
|
|
441
|
+
"success": False,
|
|
442
|
+
"error": f"Gate at ({x}, {y}) cannot have more than 2 inputs.",
|
|
443
|
+
}
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
for existing_fanin in existing_fanins:
|
|
447
|
+
# Determine allowed number of fanouts
|
|
448
|
+
existing_fanouts = layout.fanouts(existing_fanin)
|
|
449
|
+
num_fanouts = len(existing_fanouts)
|
|
450
|
+
|
|
451
|
+
if layout.is_po(existing_fanin):
|
|
452
|
+
max_fanouts = 0
|
|
453
|
+
elif layout.is_wire(existing_fanin) and not existing_fanin.z == 1:
|
|
454
|
+
max_fanouts = 2
|
|
455
|
+
else:
|
|
456
|
+
max_fanouts = 1
|
|
457
|
+
|
|
458
|
+
if num_fanouts >= max_fanouts:
|
|
459
|
+
return jsonify(
|
|
460
|
+
{
|
|
461
|
+
"success": False,
|
|
462
|
+
"error": f"Gate at {existing_fanin} cannot have more than {max_fanouts} outgoing connections.",
|
|
463
|
+
}
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# Determine allowed number of fanouts
|
|
467
|
+
existing_fanouts_first_node = layout.fanouts((first_x, first_y, first_z))
|
|
468
|
+
num_fanouts_first_node = len(existing_fanouts_first_node)
|
|
469
|
+
|
|
470
|
+
if layout.is_po(first_node):
|
|
471
|
+
max_fanouts_first_node = 0
|
|
472
|
+
elif layout.is_wire(first_node) and not first_z == 1:
|
|
473
|
+
max_fanouts_first_node = 2
|
|
474
|
+
else:
|
|
475
|
+
max_fanouts_first_node = 1
|
|
476
|
+
|
|
477
|
+
if num_fanouts_first_node >= max_fanouts_first_node:
|
|
478
|
+
return jsonify(
|
|
479
|
+
{
|
|
480
|
+
"success": False,
|
|
481
|
+
"error": f"Gate at ({first_x}, {first_y}, {first_z}) cannot have more than {max_fanouts_first_node} outgoing connections.",
|
|
482
|
+
}
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# Determine allowed number of fanouts
|
|
486
|
+
existing_fanouts_second_node = layout.fanouts((second_x, second_y, second_z))
|
|
487
|
+
num_fanouts_second_node = len(existing_fanouts_second_node)
|
|
488
|
+
|
|
489
|
+
if layout.is_po(second_node):
|
|
490
|
+
max_fanouts_second_node = 0
|
|
491
|
+
elif layout.is_wire(second_node) and not second_z == 1:
|
|
492
|
+
max_fanouts_second_node = 2
|
|
493
|
+
else:
|
|
494
|
+
max_fanouts_second_node = 1
|
|
495
|
+
|
|
496
|
+
if num_fanouts_second_node >= max_fanouts_second_node:
|
|
497
|
+
return jsonify(
|
|
498
|
+
{
|
|
499
|
+
"success": False,
|
|
500
|
+
"error": f"Gate at ({second_x}, {second_y}, {second_z}) cannot have more than {max_fanouts_second_node} outgoing connections.",
|
|
501
|
+
}
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
if gate_type == "and":
|
|
505
|
+
layout.create_and(
|
|
506
|
+
layout.make_signal(first_node),
|
|
507
|
+
layout.make_signal(second_node),
|
|
508
|
+
(x, y),
|
|
509
|
+
)
|
|
510
|
+
elif gate_type == "or":
|
|
511
|
+
layout.create_or(
|
|
512
|
+
layout.make_signal(first_node),
|
|
513
|
+
layout.make_signal(second_node),
|
|
514
|
+
(x, y),
|
|
515
|
+
)
|
|
516
|
+
elif gate_type == "nor":
|
|
517
|
+
layout.create_nor(
|
|
518
|
+
layout.make_signal(first_node),
|
|
519
|
+
layout.make_signal(second_node),
|
|
520
|
+
(x, y),
|
|
521
|
+
)
|
|
522
|
+
elif gate_type == "xor":
|
|
523
|
+
layout.create_xor(
|
|
524
|
+
layout.make_signal(first_node),
|
|
525
|
+
layout.make_signal(second_node),
|
|
526
|
+
(x, y),
|
|
527
|
+
)
|
|
528
|
+
elif gate_type == "xnor":
|
|
529
|
+
layout.create_xnor(
|
|
530
|
+
layout.make_signal(first_node),
|
|
531
|
+
layout.make_signal(second_node),
|
|
532
|
+
(x, y),
|
|
533
|
+
)
|
|
534
|
+
elif gate_type in ["bufc", "bufk"]:
|
|
535
|
+
# The upper wire always faces east: west input for straight, north input for bent crossings.
|
|
536
|
+
upper_is_first = first_x < x if gate_type == "bufc" else first_y < y
|
|
537
|
+
lower_node, upper_node = (second_node, first_node) if upper_is_first else (first_node, second_node)
|
|
538
|
+
layout.create_buf(layout.make_signal(lower_node), (x, y, 0))
|
|
539
|
+
layout.create_buf(layout.make_signal(upper_node), (x, y, 1))
|
|
540
|
+
layout.obstruct_coordinate((x, y, 1))
|
|
541
|
+
if layout.fanout_size(first_node) == 2:
|
|
542
|
+
update_first = True
|
|
543
|
+
if layout.fanout_size(second_node) == 2:
|
|
544
|
+
update_second = True
|
|
545
|
+
else:
|
|
546
|
+
return jsonify({"success": False, "error": f"Unsupported gate type: {gate_type}"})
|
|
547
|
+
|
|
548
|
+
layout.obstruct_coordinate((x, y, 0))
|
|
549
|
+
|
|
550
|
+
return (
|
|
551
|
+
jsonify(
|
|
552
|
+
{
|
|
553
|
+
"success": True,
|
|
554
|
+
"updateFirstBufToFanout": update_first,
|
|
555
|
+
"updateSecondBufToFanout": update_second,
|
|
556
|
+
}
|
|
557
|
+
),
|
|
558
|
+
200,
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
except RequestEntityTooLarge:
|
|
562
|
+
raise
|
|
563
|
+
except Exception:
|
|
564
|
+
return _internal_error_response()
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
@app.route("/delete_gate", methods=["POST"])
|
|
568
|
+
def delete_gate():
|
|
569
|
+
try:
|
|
570
|
+
data = request.json
|
|
571
|
+
x = int(data["x"])
|
|
572
|
+
y = int(data["y"])
|
|
573
|
+
|
|
574
|
+
session_id = session["session_id"]
|
|
575
|
+
layout = layouts.get(session_id)
|
|
576
|
+
if not layout:
|
|
577
|
+
return jsonify({"success": False, "error": "Layout not found."})
|
|
578
|
+
|
|
579
|
+
found = False
|
|
580
|
+
for z in (1, 0) if layout.z() > 0 else (0,):
|
|
581
|
+
tile = (x, y, z)
|
|
582
|
+
if layout.is_empty_tile(tile):
|
|
583
|
+
continue
|
|
584
|
+
found = True
|
|
585
|
+
outgoing_tiles = layout.fanouts(tile)
|
|
586
|
+
layout.clear_tile(tile)
|
|
587
|
+
layout.clear_obstructed_coordinate(tile)
|
|
588
|
+
for outgoing_tile in outgoing_tiles:
|
|
589
|
+
incoming_tiles = layout.fanins(outgoing_tile)
|
|
590
|
+
incoming_signals = [layout.make_signal(layout.get_node(inp)) for inp in incoming_tiles if inp != tile]
|
|
591
|
+
layout.move_node(layout.get_node(outgoing_tile), outgoing_tile, incoming_signals)
|
|
592
|
+
|
|
593
|
+
if found:
|
|
594
|
+
return jsonify({"success": True})
|
|
595
|
+
else:
|
|
596
|
+
return jsonify({"success": False, "error": "Gate not found at the specified position."})
|
|
597
|
+
except RequestEntityTooLarge:
|
|
598
|
+
raise
|
|
599
|
+
except Exception:
|
|
600
|
+
return _internal_error_response()
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
@app.route("/connect_gates", methods=["POST"])
|
|
604
|
+
def connect_gates():
|
|
605
|
+
try:
|
|
606
|
+
data = request.json
|
|
607
|
+
session_id = session["session_id"]
|
|
608
|
+
layout = layouts.get(session_id)
|
|
609
|
+
if not layout:
|
|
610
|
+
return jsonify({"success": False, "error": "Layout not found."})
|
|
611
|
+
|
|
612
|
+
source_x = int(data["source_x"])
|
|
613
|
+
source_y = int(data["source_y"])
|
|
614
|
+
source_z = _gate_layer(layout, source_x, source_y)
|
|
615
|
+
source_gate_type = data["source_gate_type"]
|
|
616
|
+
target_x = int(data["target_x"])
|
|
617
|
+
target_y = int(data["target_y"])
|
|
618
|
+
target_z = _gate_layer(layout, target_x, target_y)
|
|
619
|
+
target_gate_type = data["target_gate_type"]
|
|
620
|
+
find_path = data["find_path"]
|
|
621
|
+
|
|
622
|
+
if source_gate_type == "bufc":
|
|
623
|
+
if find_path:
|
|
624
|
+
return jsonify(
|
|
625
|
+
{
|
|
626
|
+
"success": False,
|
|
627
|
+
"error": "Source gate is a crossing and the outgoing direction cannot be specified, create a connected buffer first.",
|
|
628
|
+
}
|
|
629
|
+
)
|
|
630
|
+
if source_x < target_x:
|
|
631
|
+
if layout.has_southern_outgoing_signal((source_x, source_y, 0)):
|
|
632
|
+
source_z = 1
|
|
633
|
+
elif layout.has_southern_outgoing_signal((source_x, source_y, 1)):
|
|
634
|
+
source_z = 0
|
|
635
|
+
else:
|
|
636
|
+
if layout.has_northern_incoming_signal((source_x, source_y, 0)):
|
|
637
|
+
source_z = 1
|
|
638
|
+
elif layout.has_northern_incoming_signal((source_x, source_y, 1)):
|
|
639
|
+
source_z = 0
|
|
640
|
+
else:
|
|
641
|
+
source_z = 0
|
|
642
|
+
elif source_y < target_y:
|
|
643
|
+
if layout.has_eastern_outgoing_signal((source_x, source_y, 0)):
|
|
644
|
+
source_z = 1
|
|
645
|
+
elif layout.has_eastern_outgoing_signal((source_x, source_y, 1)):
|
|
646
|
+
source_z = 0
|
|
647
|
+
else:
|
|
648
|
+
if layout.has_northern_incoming_signal((source_x, source_y, 0)):
|
|
649
|
+
source_z = 0
|
|
650
|
+
elif layout.has_northern_incoming_signal((source_x, source_y, 1)):
|
|
651
|
+
source_z = 1
|
|
652
|
+
else:
|
|
653
|
+
source_z = 0
|
|
654
|
+
else:
|
|
655
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
656
|
+
|
|
657
|
+
if source_gate_type == "bufk":
|
|
658
|
+
if find_path:
|
|
659
|
+
return jsonify(
|
|
660
|
+
{
|
|
661
|
+
"success": False,
|
|
662
|
+
"error": "Source gate is a crossing and the outgoing direction cannot be specified, create a connected buffer first.",
|
|
663
|
+
}
|
|
664
|
+
)
|
|
665
|
+
if source_x < target_x:
|
|
666
|
+
if layout.has_southern_outgoing_signal((source_x, source_y, 0)):
|
|
667
|
+
source_z = 1
|
|
668
|
+
elif layout.has_southern_outgoing_signal((source_x, source_y, 1)):
|
|
669
|
+
source_z = 0
|
|
670
|
+
else:
|
|
671
|
+
if layout.has_northern_incoming_signal((source_x, source_y, 0)):
|
|
672
|
+
source_z = 0
|
|
673
|
+
elif layout.has_northern_incoming_signal((source_x, source_y, 1)):
|
|
674
|
+
source_z = 1
|
|
675
|
+
else:
|
|
676
|
+
source_z = 0
|
|
677
|
+
elif source_y < target_y:
|
|
678
|
+
if layout.has_eastern_outgoing_signal((source_x, source_y, 0)):
|
|
679
|
+
source_z = 1
|
|
680
|
+
elif layout.has_eastern_outgoing_signal((source_x, source_y, 1)):
|
|
681
|
+
source_z = 0
|
|
682
|
+
else:
|
|
683
|
+
if layout.has_northern_incoming_signal((source_x, source_y, 0)):
|
|
684
|
+
source_z = 1
|
|
685
|
+
elif layout.has_northern_incoming_signal((source_x, source_y, 1)):
|
|
686
|
+
source_z = 0
|
|
687
|
+
else:
|
|
688
|
+
source_z = 0
|
|
689
|
+
else:
|
|
690
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
691
|
+
|
|
692
|
+
if target_gate_type == "bufc":
|
|
693
|
+
if find_path:
|
|
694
|
+
return jsonify(
|
|
695
|
+
{
|
|
696
|
+
"success": False,
|
|
697
|
+
"error": "Target gate is a crossing and the incoming direction cannot be specified, create a connected buffer first.",
|
|
698
|
+
}
|
|
699
|
+
)
|
|
700
|
+
if source_x < target_x:
|
|
701
|
+
if layout.has_southern_outgoing_signal((target_x, target_y, 0)):
|
|
702
|
+
target_z = 1
|
|
703
|
+
elif layout.has_southern_outgoing_signal((target_x, target_y, 1)):
|
|
704
|
+
target_z = 0
|
|
705
|
+
else:
|
|
706
|
+
if layout.has_northern_incoming_signal((target_x, target_y, 0)):
|
|
707
|
+
target_z = 1
|
|
708
|
+
elif layout.has_northern_incoming_signal((target_x, target_y, 1)):
|
|
709
|
+
target_z = 0
|
|
710
|
+
else:
|
|
711
|
+
target_z = 0
|
|
712
|
+
elif source_y < target_y:
|
|
713
|
+
if layout.has_eastern_outgoing_signal((target_x, target_y, 0)):
|
|
714
|
+
target_z = 1
|
|
715
|
+
elif layout.has_eastern_outgoing_signal((target_x, target_y, 1)):
|
|
716
|
+
target_z = 0
|
|
717
|
+
else:
|
|
718
|
+
if layout.has_western_incoming_signal((target_x, target_y, 0)):
|
|
719
|
+
target_z = 1
|
|
720
|
+
elif layout.has_western_incoming_signal((target_x, target_y, 1)):
|
|
721
|
+
target_z = 0
|
|
722
|
+
else:
|
|
723
|
+
target_z = 0
|
|
724
|
+
else:
|
|
725
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
726
|
+
|
|
727
|
+
if target_gate_type == "bufk":
|
|
728
|
+
if find_path:
|
|
729
|
+
return jsonify(
|
|
730
|
+
{
|
|
731
|
+
"success": False,
|
|
732
|
+
"error": "Target gate is a crossing and the incoming direction cannot be specified, create a connected buffer first.",
|
|
733
|
+
}
|
|
734
|
+
)
|
|
735
|
+
if source_x < target_x:
|
|
736
|
+
if layout.has_southern_outgoing_signal((target_x, target_y, 0)):
|
|
737
|
+
target_z = 0
|
|
738
|
+
elif layout.has_southern_outgoing_signal((target_x, target_y, 1)):
|
|
739
|
+
target_z = 1
|
|
740
|
+
else:
|
|
741
|
+
if layout.has_northern_incoming_signal((target_x, target_y, 0)):
|
|
742
|
+
target_z = 1
|
|
743
|
+
elif layout.has_northern_incoming_signal((target_x, target_y, 1)):
|
|
744
|
+
target_z = 0
|
|
745
|
+
else:
|
|
746
|
+
target_z = 0
|
|
747
|
+
elif source_y < target_y:
|
|
748
|
+
if layout.has_eastern_outgoing_signal((target_x, target_y, 0)):
|
|
749
|
+
target_z = 0
|
|
750
|
+
elif layout.has_eastern_outgoing_signal((target_x, target_y, 1)):
|
|
751
|
+
target_z = 1
|
|
752
|
+
else:
|
|
753
|
+
if layout.has_western_incoming_signal((target_x, target_y, 0)):
|
|
754
|
+
target_z = 1
|
|
755
|
+
elif layout.has_western_incoming_signal((target_x, target_y, 1)):
|
|
756
|
+
target_z = 0
|
|
757
|
+
else:
|
|
758
|
+
target_z = 0
|
|
759
|
+
else:
|
|
760
|
+
return jsonify({"success": False, "error": "Something went wrong."})
|
|
761
|
+
|
|
762
|
+
source_node = layout.get_node((source_x, source_y, source_z))
|
|
763
|
+
target_node = layout.get_node((target_x, target_y, target_z))
|
|
764
|
+
|
|
765
|
+
if not source_node:
|
|
766
|
+
return jsonify({"success": False, "error": "Source gate not found."})
|
|
767
|
+
if not target_node:
|
|
768
|
+
return jsonify({"success": False, "error": "Target gate not found."})
|
|
769
|
+
|
|
770
|
+
# Determine allowed number of fanouts
|
|
771
|
+
existing_fanouts = layout.fanouts((source_x, source_y, source_z))
|
|
772
|
+
num_fanouts = len(existing_fanouts)
|
|
773
|
+
|
|
774
|
+
if layout.is_po(source_node):
|
|
775
|
+
max_fanouts = 0
|
|
776
|
+
elif layout.is_wire(source_node) and not source_z == 1:
|
|
777
|
+
max_fanouts = 2
|
|
778
|
+
else:
|
|
779
|
+
max_fanouts = 1
|
|
780
|
+
|
|
781
|
+
if num_fanouts >= max_fanouts:
|
|
782
|
+
return jsonify(
|
|
783
|
+
{
|
|
784
|
+
"success": False,
|
|
785
|
+
"error": f"Gate at ({source_x}, {source_y}, {source_z}) cannot have more than {max_fanouts} outgoing connections.",
|
|
786
|
+
}
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
# Determine allowed number of fanins
|
|
790
|
+
existing_fanins = layout.fanins((target_x, target_y, target_z))
|
|
791
|
+
num_fanins = len(existing_fanins)
|
|
792
|
+
|
|
793
|
+
if layout.is_pi(target_node):
|
|
794
|
+
max_fanins = 0
|
|
795
|
+
elif layout.is_wire(target_node) or layout.is_inv(target_node):
|
|
796
|
+
max_fanins = 1
|
|
797
|
+
else:
|
|
798
|
+
max_fanins = 2
|
|
799
|
+
|
|
800
|
+
if num_fanins >= max_fanins:
|
|
801
|
+
return jsonify(
|
|
802
|
+
{
|
|
803
|
+
"success": False,
|
|
804
|
+
"error": f"Gate at ({target_x}, {target_y}, {target_z}) cannot have more than {max_fanins} incoming connections.",
|
|
805
|
+
}
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
if (source_x, source_y, source_z) in existing_fanins:
|
|
809
|
+
return jsonify(
|
|
810
|
+
{
|
|
811
|
+
"success": False,
|
|
812
|
+
"error": f"Gate at ({target_x}, {target_y}, {target_z}) is already connected to ({source_x}, {source_y}, {source_z}.",
|
|
813
|
+
}
|
|
814
|
+
)
|
|
815
|
+
else:
|
|
816
|
+
if not find_path:
|
|
817
|
+
existing_fanins.append((source_x, source_y, source_z))
|
|
818
|
+
|
|
819
|
+
incoming_signals = []
|
|
820
|
+
for fanin in existing_fanins:
|
|
821
|
+
incoming_signals.append(layout.make_signal(layout.get_node(fanin)))
|
|
822
|
+
|
|
823
|
+
if find_path:
|
|
824
|
+
path = a_star(layout, (source_x, source_y, source_z), (target_x, target_y, target_z))
|
|
825
|
+
|
|
826
|
+
if not path:
|
|
827
|
+
return jsonify(
|
|
828
|
+
{
|
|
829
|
+
"success": False,
|
|
830
|
+
"error": "No (crossing-free) path found between the selected gates.",
|
|
831
|
+
}
|
|
832
|
+
)
|
|
833
|
+
else:
|
|
834
|
+
path = [(source_x, source_y, source_z), (target_x, target_y, target_z)]
|
|
835
|
+
|
|
836
|
+
if find_path:
|
|
837
|
+
route_path(layout, path)
|
|
838
|
+
else:
|
|
839
|
+
layout.move_node(target_node, (target_x, target_y, target_z), incoming_signals)
|
|
840
|
+
|
|
841
|
+
if layout.fanout_size(source_node) == 2:
|
|
842
|
+
update = True
|
|
843
|
+
else:
|
|
844
|
+
update = False
|
|
845
|
+
|
|
846
|
+
if find_path:
|
|
847
|
+
for coord in path:
|
|
848
|
+
layout.obstruct_coordinate(coord)
|
|
849
|
+
|
|
850
|
+
return (
|
|
851
|
+
jsonify(
|
|
852
|
+
{
|
|
853
|
+
"success": True,
|
|
854
|
+
"updateBufToFanout": update,
|
|
855
|
+
"path": [(coord.x, coord.y) for coord in path]
|
|
856
|
+
if data["find_path"]
|
|
857
|
+
else [(source_x, source_y), (target_x, target_y)],
|
|
858
|
+
}
|
|
859
|
+
),
|
|
860
|
+
200,
|
|
861
|
+
)
|
|
862
|
+
except RequestEntityTooLarge:
|
|
863
|
+
raise
|
|
864
|
+
except Exception:
|
|
865
|
+
return _internal_error_response()
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
@app.route("/move_gate", methods=["POST"])
|
|
869
|
+
def move_gate():
|
|
870
|
+
try:
|
|
871
|
+
data = request.json
|
|
872
|
+
session_id = session["session_id"]
|
|
873
|
+
layout = layouts.get(session_id)
|
|
874
|
+
if not layout:
|
|
875
|
+
return jsonify({"success": False, "error": "Layout not found."})
|
|
876
|
+
|
|
877
|
+
source_x = int(data["source_x"])
|
|
878
|
+
source_y = int(data["source_y"])
|
|
879
|
+
source_gate_type = data["source_gate_type"]
|
|
880
|
+
source_z = _gate_layer(layout, source_x, source_y) if source_gate_type not in ("bufc", "bufk") else 1
|
|
881
|
+
source = (source_x, source_y, source_z)
|
|
882
|
+
target_x = int(data["target_x"])
|
|
883
|
+
target_y = int(data["target_y"])
|
|
884
|
+
target_z = source_z
|
|
885
|
+
target = (target_x, target_y, target_z)
|
|
886
|
+
|
|
887
|
+
if not (0 <= target_x <= layout.x() and 0 <= target_y <= layout.y()):
|
|
888
|
+
return jsonify({"success": False, "error": "Target tile is outside the layout."}), 400
|
|
889
|
+
if not layout.is_empty_tile((target_x, target_y, 0)) or not layout.is_empty_tile((target_x, target_y, 1)):
|
|
890
|
+
return jsonify({"success": False, "error": "Target tile already has a gate."}), 400
|
|
891
|
+
if source_gate_type in ("bufc", "bufk") and layout.is_empty_tile((source_x, source_y, 0)):
|
|
892
|
+
return jsonify({"success": False, "error": "Crossing gate is incomplete."}), 400
|
|
893
|
+
|
|
894
|
+
source_node = layout.get_node(source)
|
|
895
|
+
|
|
896
|
+
if not source_node:
|
|
897
|
+
return jsonify({"success": False, "error": "Source gate not found."})
|
|
898
|
+
|
|
899
|
+
# Find all gates that use this node as an input signal
|
|
900
|
+
outgoing_tiles = layout.fanouts(source)
|
|
901
|
+
|
|
902
|
+
layout.move_node(source_node, target, [])
|
|
903
|
+
layout.clear_obstructed_coordinate(source)
|
|
904
|
+
layout.obstruct_coordinate(target)
|
|
905
|
+
|
|
906
|
+
# Update signals for dependent nodes
|
|
907
|
+
for outgoing_tile in outgoing_tiles:
|
|
908
|
+
# Get the other input signals, if any
|
|
909
|
+
incoming_tiles = layout.fanins(outgoing_tile)
|
|
910
|
+
incoming_signals = [layout.make_signal(layout.get_node(inp)) for inp in incoming_tiles if inp != source]
|
|
911
|
+
layout.move_node(layout.get_node(outgoing_tile), outgoing_tile, incoming_signals)
|
|
912
|
+
|
|
913
|
+
if source_gate_type in ("bufc", "bufk"):
|
|
914
|
+
source_z = 0
|
|
915
|
+
source = (source_x, source_y, source_z)
|
|
916
|
+
target_z = 0
|
|
917
|
+
target = (target_x, target_y, target_z)
|
|
918
|
+
|
|
919
|
+
source_node = layout.get_node(source)
|
|
920
|
+
|
|
921
|
+
if not source_node:
|
|
922
|
+
return jsonify({"success": False, "error": "Source gate not found."})
|
|
923
|
+
|
|
924
|
+
# Find all gates that use this node as an input signal
|
|
925
|
+
outgoing_tiles = layout.fanouts(source)
|
|
926
|
+
layout.move_node(source_node, target, [])
|
|
927
|
+
layout.clear_tile(source)
|
|
928
|
+
layout.clear_obstructed_coordinate(source)
|
|
929
|
+
layout.obstruct_coordinate(target)
|
|
930
|
+
|
|
931
|
+
# Update signals for dependent nodes
|
|
932
|
+
for outgoing_tile in outgoing_tiles:
|
|
933
|
+
# Get the other input signals, if any
|
|
934
|
+
incoming_tiles = layout.fanins(outgoing_tile)
|
|
935
|
+
incoming_signals = [layout.make_signal(layout.get_node(inp)) for inp in incoming_tiles if inp != source]
|
|
936
|
+
layout.move_node(layout.get_node(outgoing_tile), outgoing_tile, incoming_signals)
|
|
937
|
+
|
|
938
|
+
return (
|
|
939
|
+
jsonify(
|
|
940
|
+
{
|
|
941
|
+
"success": True,
|
|
942
|
+
"updateGateType": source_gate_type == "fanout",
|
|
943
|
+
}
|
|
944
|
+
),
|
|
945
|
+
200,
|
|
946
|
+
)
|
|
947
|
+
except RequestEntityTooLarge:
|
|
948
|
+
raise
|
|
949
|
+
except Exception:
|
|
950
|
+
return _internal_error_response()
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
@app.route("/check_design_rules", methods=["POST"])
|
|
954
|
+
def check_design_rules():
|
|
955
|
+
try:
|
|
956
|
+
session_id = session["session_id"]
|
|
957
|
+
layout = layouts.get(session_id)
|
|
958
|
+
if not layout:
|
|
959
|
+
return jsonify({"success": False, "error": "Layout not found."})
|
|
960
|
+
|
|
961
|
+
warnings, errors, report = check_design_rules_function(layout)
|
|
962
|
+
|
|
963
|
+
return (
|
|
964
|
+
jsonify(
|
|
965
|
+
{
|
|
966
|
+
"success": True,
|
|
967
|
+
"errors": errors,
|
|
968
|
+
"warnings": warnings,
|
|
969
|
+
"report": report,
|
|
970
|
+
}
|
|
971
|
+
),
|
|
972
|
+
200,
|
|
973
|
+
)
|
|
974
|
+
except Exception:
|
|
975
|
+
return _internal_error_response()
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def strip_ansi_codes(text):
|
|
979
|
+
ansi_escape = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]")
|
|
980
|
+
return ansi_escape.sub("", text)
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
def format_output(text):
|
|
984
|
+
# Split the text based on the [i] marker
|
|
985
|
+
sections = text.split("[i]")
|
|
986
|
+
|
|
987
|
+
# Reconstruct the output with proper line breaks
|
|
988
|
+
formatted = ""
|
|
989
|
+
for section in sections:
|
|
990
|
+
stripped = section.strip()
|
|
991
|
+
if stripped:
|
|
992
|
+
formatted += f"[i] {stripped}\n"
|
|
993
|
+
return formatted
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
def check_design_rules_function(layout):
|
|
997
|
+
# Create a StringIO object to capture the output
|
|
998
|
+
captured_output = io.StringIO()
|
|
999
|
+
|
|
1000
|
+
# Redirect stdout to the StringIO object
|
|
1001
|
+
with redirect_stdout(captured_output):
|
|
1002
|
+
warnings, errors = gate_level_drvs(layout, print_report=True)
|
|
1003
|
+
|
|
1004
|
+
# Retrieve the captured output
|
|
1005
|
+
raw_output = captured_output.getvalue()
|
|
1006
|
+
clean_output = strip_ansi_codes(raw_output)
|
|
1007
|
+
output = format_output(clean_output)
|
|
1008
|
+
return warnings, errors, output
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
@app.route("/check_equivalence", methods=["POST"])
|
|
1012
|
+
def check_equivalence():
|
|
1013
|
+
try:
|
|
1014
|
+
session_id = session.get("session_id")
|
|
1015
|
+
if not session_id:
|
|
1016
|
+
return jsonify({"success": False, "error": "Session not found."}), 400
|
|
1017
|
+
|
|
1018
|
+
layout = layouts.get(session_id)
|
|
1019
|
+
if not layout:
|
|
1020
|
+
return jsonify({"success": False, "error": "Layout not found."}), 404
|
|
1021
|
+
|
|
1022
|
+
network = networks.get(session_id)
|
|
1023
|
+
if not network:
|
|
1024
|
+
return jsonify({"success": False, "error": "Network not found."}), 404
|
|
1025
|
+
|
|
1026
|
+
# Call your design rule checking function
|
|
1027
|
+
equivalence, counter_example = check_equivalence_function(layout, network)
|
|
1028
|
+
|
|
1029
|
+
return (
|
|
1030
|
+
jsonify(
|
|
1031
|
+
{
|
|
1032
|
+
"success": True,
|
|
1033
|
+
"equivalence": equivalence,
|
|
1034
|
+
"counter_example": counter_example,
|
|
1035
|
+
}
|
|
1036
|
+
),
|
|
1037
|
+
200,
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
except Exception:
|
|
1041
|
+
return _internal_error_response(), 500
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
def check_equivalence_function(layout, network):
|
|
1045
|
+
stats = equivalence_checking_stats()
|
|
1046
|
+
eq = equivalence_checking(layout, network, stats)
|
|
1047
|
+
|
|
1048
|
+
if eq == eq_type.STRONG:
|
|
1049
|
+
equivalence = "STRONG"
|
|
1050
|
+
counter_example = None # No counter example needed
|
|
1051
|
+
elif eq == eq_type.WEAK:
|
|
1052
|
+
equivalence = "WEAK"
|
|
1053
|
+
counter_example = None # No counter example needed
|
|
1054
|
+
else:
|
|
1055
|
+
equivalence = "NO"
|
|
1056
|
+
counter_example = stats.counter_example
|
|
1057
|
+
print(counter_example)
|
|
1058
|
+
|
|
1059
|
+
return equivalence, counter_example
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
def _send_layout_file(layout, writer, filename, mimetype, *params):
|
|
1063
|
+
# Each request owns its files; read the result before the temporary directory is removed.
|
|
1064
|
+
with tempfile.TemporaryDirectory(prefix="mnt-designer-") as directory:
|
|
1065
|
+
path = Path(directory) / filename
|
|
1066
|
+
writer(layout, str(path), *params)
|
|
1067
|
+
content = io.BytesIO(path.read_bytes())
|
|
1068
|
+
return send_file(content, as_attachment=True, mimetype=mimetype, download_name=filename)
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
@app.route("/export_layout", methods=["GET"])
|
|
1072
|
+
def export_layout():
|
|
1073
|
+
try:
|
|
1074
|
+
session_id = session.get("session_id")
|
|
1075
|
+
layout = layouts.get(session_id)
|
|
1076
|
+
|
|
1077
|
+
if not layout:
|
|
1078
|
+
return jsonify({"success": False, "error": "Layout not found."})
|
|
1079
|
+
|
|
1080
|
+
return _send_layout_file(layout, write_fgl_layout, "layout.fgl", "application/fgl")
|
|
1081
|
+
|
|
1082
|
+
except Exception:
|
|
1083
|
+
return _internal_error_response()
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
@app.route("/export_dot_layout", methods=["GET"])
|
|
1087
|
+
def export_dot_layout():
|
|
1088
|
+
try:
|
|
1089
|
+
session_id = session.get("session_id")
|
|
1090
|
+
layout = layouts.get(session_id)
|
|
1091
|
+
|
|
1092
|
+
if not layout:
|
|
1093
|
+
return jsonify({"success": False, "error": "Layout not found."})
|
|
1094
|
+
|
|
1095
|
+
return _send_layout_file(layout, write_dot_layout, "layout.dot", "application/dot")
|
|
1096
|
+
|
|
1097
|
+
except Exception:
|
|
1098
|
+
return _internal_error_response()
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
@app.route("/export_qca_layout", methods=["GET"])
|
|
1102
|
+
def export_qca_layout():
|
|
1103
|
+
try:
|
|
1104
|
+
session_id = session.get("session_id")
|
|
1105
|
+
layout = layouts.get(session_id)
|
|
1106
|
+
|
|
1107
|
+
if not layout:
|
|
1108
|
+
return jsonify({"success": False, "error": "Layout not found."})
|
|
1109
|
+
|
|
1110
|
+
cell_level_layout = apply_qca_one_library(layout)
|
|
1111
|
+
params = write_qca_layout_svg_params()
|
|
1112
|
+
return _send_layout_file(cell_level_layout, write_qca_layout_svg, "layout_qca.svg", "image/svg+xml", params)
|
|
1113
|
+
|
|
1114
|
+
except Exception:
|
|
1115
|
+
return _internal_error_response()
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
@app.route("/export_sidb_layout", methods=["GET"])
|
|
1119
|
+
def export_sidb_layout():
|
|
1120
|
+
try:
|
|
1121
|
+
session_id = session.get("session_id")
|
|
1122
|
+
layout = layouts.get(session_id)
|
|
1123
|
+
|
|
1124
|
+
if not layout:
|
|
1125
|
+
return jsonify({"success": False, "error": "Layout not found."})
|
|
1126
|
+
|
|
1127
|
+
hex_layout = hexagonalization(layout)
|
|
1128
|
+
cell_level_layout = apply_bestagon_library(hex_layout)
|
|
1129
|
+
|
|
1130
|
+
params = write_sidb_layout_svg_params()
|
|
1131
|
+
params.color_background = color_mode.DARK
|
|
1132
|
+
return _send_layout_file(cell_level_layout, write_sidb_layout_svg, "layout_sidb.svg", "image/svg+xml", params)
|
|
1133
|
+
|
|
1134
|
+
except Exception:
|
|
1135
|
+
return _internal_error_response()
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
@app.route("/import_layout", methods=["POST"])
|
|
1139
|
+
def import_layout():
|
|
1140
|
+
try:
|
|
1141
|
+
# Get the uploaded file with the key 'file'
|
|
1142
|
+
file = request.files.get("file")
|
|
1143
|
+
if not file:
|
|
1144
|
+
return jsonify({"success": False, "error": "No file provided."})
|
|
1145
|
+
|
|
1146
|
+
with tempfile.TemporaryDirectory(prefix="mnt-designer-") as directory:
|
|
1147
|
+
path = Path(directory) / "layout.fgl"
|
|
1148
|
+
file.save(path)
|
|
1149
|
+
layout = read_cartesian_fgl_layout(str(path))
|
|
1150
|
+
|
|
1151
|
+
# Override the current layout with the imported layout
|
|
1152
|
+
session_id = session["session_id"]
|
|
1153
|
+
layouts[session_id] = cartesian_obstruction_layout(layout)
|
|
1154
|
+
|
|
1155
|
+
return jsonify({"success": True})
|
|
1156
|
+
|
|
1157
|
+
except RequestEntityTooLarge:
|
|
1158
|
+
raise
|
|
1159
|
+
except Exception:
|
|
1160
|
+
return _internal_error_response()
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
@app.route("/get_layout", methods=["GET"])
|
|
1164
|
+
def get_layout():
|
|
1165
|
+
try:
|
|
1166
|
+
session_id = session["session_id"]
|
|
1167
|
+
layout = layouts.get(session_id)
|
|
1168
|
+
if not layout:
|
|
1169
|
+
return jsonify({"success": False, "error": "Layout not found."})
|
|
1170
|
+
|
|
1171
|
+
# Extract layout data
|
|
1172
|
+
layout_dimensions, gates = get_layout_information(layout)
|
|
1173
|
+
return jsonify({"success": True, "layoutDimensions": layout_dimensions, "gates": gates})
|
|
1174
|
+
except Exception:
|
|
1175
|
+
return _internal_error_response()
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
@app.route("/get_bounding_box", methods=["GET"])
|
|
1179
|
+
def get_bounding_box():
|
|
1180
|
+
try:
|
|
1181
|
+
session_id = session["session_id"]
|
|
1182
|
+
layout = layouts.get(session_id)
|
|
1183
|
+
if not layout:
|
|
1184
|
+
return jsonify({"success": True, "max_x": 0, "max_y": 0})
|
|
1185
|
+
|
|
1186
|
+
# Extract layout data
|
|
1187
|
+
_, max_coord = layout.bounding_box_2d()
|
|
1188
|
+
return jsonify({"success": True, "max_x": max_coord.x, "max_y": max_coord.y})
|
|
1189
|
+
except Exception:
|
|
1190
|
+
return _internal_error_response()
|
|
1191
|
+
|
|
1192
|
+
|
|
1193
|
+
@app.route("/get_verilog_code", methods=["GET"])
|
|
1194
|
+
def get_verilog_code():
|
|
1195
|
+
try:
|
|
1196
|
+
# Ensure the user has a session_id
|
|
1197
|
+
if "session_id" not in session:
|
|
1198
|
+
return jsonify({"success": False, "error": "Session not found."}), 400
|
|
1199
|
+
|
|
1200
|
+
session_id = session["session_id"]
|
|
1201
|
+
|
|
1202
|
+
# Retrieve the Verilog code for the current session
|
|
1203
|
+
code = verilogs.get(session_id)
|
|
1204
|
+
|
|
1205
|
+
if code is None:
|
|
1206
|
+
return jsonify({"success": False, "error": "Verilog code not found."})
|
|
1207
|
+
|
|
1208
|
+
# Return the Verilog code
|
|
1209
|
+
return jsonify({"success": True, "code": code}), 200
|
|
1210
|
+
|
|
1211
|
+
except Exception:
|
|
1212
|
+
return _internal_error_response(), 500
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
def _read_verilog(code):
|
|
1216
|
+
with tempfile.TemporaryDirectory(prefix="mnt-designer-") as directory:
|
|
1217
|
+
path = Path(directory) / "network.v"
|
|
1218
|
+
path.write_text(code, encoding="utf-8")
|
|
1219
|
+
return read_technology_network(str(path))
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
@app.route("/save_verilog_code", methods=["POST"])
|
|
1223
|
+
def save_verilog_code():
|
|
1224
|
+
try:
|
|
1225
|
+
data = request.json
|
|
1226
|
+
code = data.get("code", "")
|
|
1227
|
+
|
|
1228
|
+
network = _read_verilog(code)
|
|
1229
|
+
|
|
1230
|
+
session_id = session["session_id"]
|
|
1231
|
+
networks[session_id] = network
|
|
1232
|
+
verilogs[session_id] = code
|
|
1233
|
+
|
|
1234
|
+
return jsonify({"success": True})
|
|
1235
|
+
except RequestEntityTooLarge:
|
|
1236
|
+
raise
|
|
1237
|
+
except Exception:
|
|
1238
|
+
return _internal_error_response()
|
|
1239
|
+
|
|
1240
|
+
|
|
1241
|
+
@app.route("/import_verilog_code", methods=["POST"])
|
|
1242
|
+
def import_verilog_code():
|
|
1243
|
+
try:
|
|
1244
|
+
# Get the uploaded file with the key 'file'
|
|
1245
|
+
uploaded_file = request.files.get("file")
|
|
1246
|
+
if not uploaded_file:
|
|
1247
|
+
return jsonify({"success": False, "error": "No file provided."})
|
|
1248
|
+
|
|
1249
|
+
# Read the file content
|
|
1250
|
+
code = uploaded_file.read().decode("utf-8")
|
|
1251
|
+
|
|
1252
|
+
network = _read_verilog(code)
|
|
1253
|
+
|
|
1254
|
+
# Store the network in the session
|
|
1255
|
+
session_id = session["session_id"]
|
|
1256
|
+
networks[session_id] = network
|
|
1257
|
+
verilogs[session_id] = code
|
|
1258
|
+
|
|
1259
|
+
# Return the code to be displayed in the editor
|
|
1260
|
+
return jsonify({"success": True, "code": code})
|
|
1261
|
+
|
|
1262
|
+
except RequestEntityTooLarge:
|
|
1263
|
+
raise
|
|
1264
|
+
except Exception:
|
|
1265
|
+
return _internal_error_response()
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
@app.route("/apply_orthogonal", methods=["POST"])
|
|
1269
|
+
def apply_orthogonal():
|
|
1270
|
+
try:
|
|
1271
|
+
session_id = session["session_id"]
|
|
1272
|
+
network = networks.get(session_id)
|
|
1273
|
+
if not network:
|
|
1274
|
+
return jsonify(
|
|
1275
|
+
{
|
|
1276
|
+
"success": False,
|
|
1277
|
+
"error": "Network not found. Please save or import Verilog code first.",
|
|
1278
|
+
}
|
|
1279
|
+
)
|
|
1280
|
+
|
|
1281
|
+
if network.size() < 3:
|
|
1282
|
+
return jsonify(
|
|
1283
|
+
{
|
|
1284
|
+
"success": False,
|
|
1285
|
+
"error": "Network size is less than 3, indicating that not gates are present.",
|
|
1286
|
+
}
|
|
1287
|
+
)
|
|
1288
|
+
|
|
1289
|
+
if network.size() > 500:
|
|
1290
|
+
return jsonify(
|
|
1291
|
+
{
|
|
1292
|
+
"success": False,
|
|
1293
|
+
"error": "Network size exceeds 500 nodes and the resulting layout can not be rendered.",
|
|
1294
|
+
}
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1297
|
+
for po in network.pos():
|
|
1298
|
+
for fanin in network.fanins(po):
|
|
1299
|
+
if fanin in (0, 1):
|
|
1300
|
+
return jsonify(
|
|
1301
|
+
{
|
|
1302
|
+
"success": False,
|
|
1303
|
+
"error": f"Network has an unconnected PO: {network.get_output_name(network.po_index(po))}.",
|
|
1304
|
+
}
|
|
1305
|
+
)
|
|
1306
|
+
|
|
1307
|
+
layout = orthogonal(network)
|
|
1308
|
+
|
|
1309
|
+
layouts[session_id] = cartesian_obstruction_layout(layout) # Update the layout in the session
|
|
1310
|
+
|
|
1311
|
+
layout_dimensions, gates = get_layout_information(layout)
|
|
1312
|
+
|
|
1313
|
+
return jsonify({"success": True, "layoutDimensions": layout_dimensions, "gates": gates})
|
|
1314
|
+
except Exception:
|
|
1315
|
+
return _internal_error_response()
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
@app.route("/apply_iosdn", methods=["POST"])
|
|
1319
|
+
def apply_iosdn():
|
|
1320
|
+
try:
|
|
1321
|
+
session_id = session["session_id"]
|
|
1322
|
+
network = networks.get(session_id)
|
|
1323
|
+
if not network:
|
|
1324
|
+
return jsonify(
|
|
1325
|
+
{
|
|
1326
|
+
"success": False,
|
|
1327
|
+
"error": "Network not found. Please save or import Verilog code first.",
|
|
1328
|
+
}
|
|
1329
|
+
)
|
|
1330
|
+
|
|
1331
|
+
if network.size() < 3:
|
|
1332
|
+
return jsonify(
|
|
1333
|
+
{
|
|
1334
|
+
"success": False,
|
|
1335
|
+
"error": "Network size is less than 3, indicating that not gates are present.",
|
|
1336
|
+
}
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
if network.size() > 500:
|
|
1340
|
+
return jsonify(
|
|
1341
|
+
{
|
|
1342
|
+
"success": False,
|
|
1343
|
+
"error": "Network size exceeds 500 nodes and the resulting layout can not be rendered.",
|
|
1344
|
+
}
|
|
1345
|
+
)
|
|
1346
|
+
|
|
1347
|
+
for po in network.pos():
|
|
1348
|
+
for fanin in network.fanins(po):
|
|
1349
|
+
if fanin in (0, 1):
|
|
1350
|
+
return jsonify(
|
|
1351
|
+
{
|
|
1352
|
+
"success": False,
|
|
1353
|
+
"error": f"Network has an unconnected PO: {network.get_output_name(network.po_index(po))}.",
|
|
1354
|
+
}
|
|
1355
|
+
)
|
|
1356
|
+
|
|
1357
|
+
return jsonify({"success": False, "error": "Input-ordering SDN not available in pyfiction yet."})
|
|
1358
|
+
except Exception:
|
|
1359
|
+
return _internal_error_response()
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
@app.route("/apply_gold", methods=["POST"])
|
|
1363
|
+
def apply_gold():
|
|
1364
|
+
try:
|
|
1365
|
+
session_id = session["session_id"]
|
|
1366
|
+
network = networks.get(session_id)
|
|
1367
|
+
if not network:
|
|
1368
|
+
return jsonify(
|
|
1369
|
+
{
|
|
1370
|
+
"success": False,
|
|
1371
|
+
"error": "Network not found. Please save or import Verilog code first.",
|
|
1372
|
+
}
|
|
1373
|
+
)
|
|
1374
|
+
|
|
1375
|
+
if network.size() < 3:
|
|
1376
|
+
return jsonify(
|
|
1377
|
+
{
|
|
1378
|
+
"success": False,
|
|
1379
|
+
"error": "Network is empty.",
|
|
1380
|
+
}
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
if network.size() > 150:
|
|
1384
|
+
return jsonify({"success": False, "error": "Network size exceeds 150 nodes."})
|
|
1385
|
+
|
|
1386
|
+
for po in network.pos():
|
|
1387
|
+
for fanin in network.fanins(po):
|
|
1388
|
+
if fanin in (0, 1):
|
|
1389
|
+
return jsonify(
|
|
1390
|
+
{
|
|
1391
|
+
"success": False,
|
|
1392
|
+
"error": f"Network has an unconnected PO: {network.get_output_name(network.po_index(po))}.",
|
|
1393
|
+
}
|
|
1394
|
+
)
|
|
1395
|
+
|
|
1396
|
+
# Parameters
|
|
1397
|
+
data = request.json
|
|
1398
|
+
params = graph_oriented_layout_design_params()
|
|
1399
|
+
params.return_first = bool(data.get("return_first"))
|
|
1400
|
+
|
|
1401
|
+
mode = data.get("mode")
|
|
1402
|
+
if mode == "HIGH_EFFICIENCY":
|
|
1403
|
+
params.mode = gold_effort_mode.HIGH_EFFICIENCY
|
|
1404
|
+
elif mode == "HIGH_EFFORT":
|
|
1405
|
+
params.mode = gold_effort_mode.HIGH_EFFORT
|
|
1406
|
+
elif mode == "HIGHEST_EFFORT":
|
|
1407
|
+
params.mode = gold_effort_mode.HIGHEST_EFFORT
|
|
1408
|
+
else:
|
|
1409
|
+
return jsonify({"success": False, "error": f"Unknown mode: {mode}."})
|
|
1410
|
+
|
|
1411
|
+
params.timeout = int(data.get("timeout"))
|
|
1412
|
+
params.num_vertex_expansions = int(data.get("num_vertex_expansions"))
|
|
1413
|
+
params.planar = bool(data.get("planar"))
|
|
1414
|
+
params.enable_multithreading = bool(data.get("enable_multithreading", params.enable_multithreading))
|
|
1415
|
+
|
|
1416
|
+
cost = data.get("cost")
|
|
1417
|
+
if cost == "AREA":
|
|
1418
|
+
params.cost = gold_cost_objective.AREA
|
|
1419
|
+
elif cost == "WIRES":
|
|
1420
|
+
params.cost = gold_cost_objective.WIRES
|
|
1421
|
+
elif cost == "CROSSINGS":
|
|
1422
|
+
params.cost = gold_cost_objective.CROSSINGS
|
|
1423
|
+
elif cost == "ACP":
|
|
1424
|
+
params.cost = gold_cost_objective.ACP
|
|
1425
|
+
else:
|
|
1426
|
+
return jsonify({"success": False, "error": f"Unknown cost objective: {cost}."})
|
|
1427
|
+
|
|
1428
|
+
layout = graph_oriented_layout_design(network, params)
|
|
1429
|
+
if layout:
|
|
1430
|
+
layouts[session_id] = cartesian_obstruction_layout(layout) # Update the layout in the session
|
|
1431
|
+
layout_dimensions, gates = get_layout_information(layout)
|
|
1432
|
+
|
|
1433
|
+
return jsonify({"success": True, "layoutDimensions": layout_dimensions, "gates": gates})
|
|
1434
|
+
else:
|
|
1435
|
+
return jsonify(
|
|
1436
|
+
{
|
|
1437
|
+
"success": False,
|
|
1438
|
+
"error": "No layout found with the specified parameters.",
|
|
1439
|
+
}
|
|
1440
|
+
)
|
|
1441
|
+
except RequestEntityTooLarge:
|
|
1442
|
+
raise
|
|
1443
|
+
except Exception:
|
|
1444
|
+
return _internal_error_response()
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
@app.route("/apply_exact", methods=["POST"])
|
|
1448
|
+
def apply_exact():
|
|
1449
|
+
try:
|
|
1450
|
+
session_id = session["session_id"]
|
|
1451
|
+
network = networks.get(session_id)
|
|
1452
|
+
if not network:
|
|
1453
|
+
return jsonify(
|
|
1454
|
+
{
|
|
1455
|
+
"success": False,
|
|
1456
|
+
"error": "Network not found. Please save or import Verilog code first.",
|
|
1457
|
+
}
|
|
1458
|
+
)
|
|
1459
|
+
|
|
1460
|
+
if network.size() < 3:
|
|
1461
|
+
return jsonify(
|
|
1462
|
+
{
|
|
1463
|
+
"success": False,
|
|
1464
|
+
"error": "Network is empty.",
|
|
1465
|
+
}
|
|
1466
|
+
)
|
|
1467
|
+
|
|
1468
|
+
if network.size() > 30:
|
|
1469
|
+
return jsonify({"success": False, "error": "Network size exceeds 30 nodes."})
|
|
1470
|
+
|
|
1471
|
+
for po in network.pos():
|
|
1472
|
+
for fanin in network.fanins(po):
|
|
1473
|
+
if fanin in (0, 1):
|
|
1474
|
+
return jsonify(
|
|
1475
|
+
{
|
|
1476
|
+
"success": False,
|
|
1477
|
+
"error": f"Network has an unconnected PO: {network.get_output_name(network.po_index(po))}.",
|
|
1478
|
+
}
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
if exact_params is None or exact_cartesian is None:
|
|
1482
|
+
return jsonify(
|
|
1483
|
+
{
|
|
1484
|
+
"success": False,
|
|
1485
|
+
"error": "Pyfiction was installed without Z3 enabled.",
|
|
1486
|
+
}
|
|
1487
|
+
)
|
|
1488
|
+
# Parameters
|
|
1489
|
+
data = request.json
|
|
1490
|
+
if not isinstance(data, dict):
|
|
1491
|
+
return jsonify({"success": False, "error": "Exact parameters must be a JSON object."}), 400
|
|
1492
|
+
params = exact_params()
|
|
1493
|
+
params.scheme = "2DDWave"
|
|
1494
|
+
# Match the native uint16_t bounds, size_t thread count, and unsigned timeout.
|
|
1495
|
+
limits = {
|
|
1496
|
+
"upper_bound_x": 65535,
|
|
1497
|
+
"upper_bound_y": 65535,
|
|
1498
|
+
"num_threads": (1 << (8 * struct.calcsize("P"))) - 1,
|
|
1499
|
+
"timeout": 4294967295,
|
|
1500
|
+
}
|
|
1501
|
+
for name, maximum in limits.items():
|
|
1502
|
+
value = data.get(name, getattr(params, name))
|
|
1503
|
+
if type(value) is not int or not 1 <= value <= maximum:
|
|
1504
|
+
return jsonify({"success": False, "error": f"{name} must be an integer between 1 and {maximum}."}), 400
|
|
1505
|
+
setattr(params, name, value)
|
|
1506
|
+
params.fixed_size = bool(data.get("fixed_size", False))
|
|
1507
|
+
params.crossings = bool(data.get("crossings", True))
|
|
1508
|
+
params.border_io = bool(data.get("border_io", True))
|
|
1509
|
+
params.straight_inverters = bool(data.get("straight_inverters", False))
|
|
1510
|
+
params.desynchronize = bool(data.get("desynchronize", True))
|
|
1511
|
+
params.minimize_wires = bool(data.get("minimize_wires", False))
|
|
1512
|
+
params.minimize_crossings = bool(data.get("minimize_crossings", False))
|
|
1513
|
+
# Now run the exact algorithm
|
|
1514
|
+
layout = exact_cartesian(network, params)
|
|
1515
|
+
if layout:
|
|
1516
|
+
layouts[session_id] = cartesian_obstruction_layout(layout)
|
|
1517
|
+
layout_dimensions, gates = get_layout_information(layout)
|
|
1518
|
+
|
|
1519
|
+
return jsonify({"success": True, "layoutDimensions": layout_dimensions, "gates": gates})
|
|
1520
|
+
else:
|
|
1521
|
+
return jsonify(
|
|
1522
|
+
{
|
|
1523
|
+
"success": False,
|
|
1524
|
+
"error": "No layout found with the specified parameters.",
|
|
1525
|
+
}
|
|
1526
|
+
)
|
|
1527
|
+
except RequestEntityTooLarge:
|
|
1528
|
+
raise
|
|
1529
|
+
except Exception:
|
|
1530
|
+
return _internal_error_response()
|
|
1531
|
+
|
|
1532
|
+
|
|
1533
|
+
@app.route("/apply_optimization", methods=["POST"])
|
|
1534
|
+
def apply_optimization():
|
|
1535
|
+
try:
|
|
1536
|
+
session_id = session["session_id"]
|
|
1537
|
+
layout = layouts.get(session_id)
|
|
1538
|
+
params = post_layout_optimization_params()
|
|
1539
|
+
|
|
1540
|
+
if not layout:
|
|
1541
|
+
return jsonify(
|
|
1542
|
+
{
|
|
1543
|
+
"success": False,
|
|
1544
|
+
"error": "Layout not found. Please create a layout first",
|
|
1545
|
+
}
|
|
1546
|
+
)
|
|
1547
|
+
|
|
1548
|
+
warnings, errors, report = check_design_rules_function(layout)
|
|
1549
|
+
if errors != 0:
|
|
1550
|
+
return jsonify(
|
|
1551
|
+
{
|
|
1552
|
+
"success": False,
|
|
1553
|
+
"error": f"Layout has {errors} errors. Fix them first before optimizing.",
|
|
1554
|
+
}
|
|
1555
|
+
)
|
|
1556
|
+
if warnings != 0:
|
|
1557
|
+
for x in range(layout.x() + 1):
|
|
1558
|
+
for y in range(layout.y() + 1):
|
|
1559
|
+
for z in range(layout.z() + 1):
|
|
1560
|
+
if not layout.is_empty_tile((x, y, z)) and layout.is_dead(layout.get_node((x, y, z))):
|
|
1561
|
+
return jsonify(
|
|
1562
|
+
{
|
|
1563
|
+
"success": False,
|
|
1564
|
+
"error": f"Layout has a dead node: ({x}, {y}, {z}). Fix it first before optimizing.",
|
|
1565
|
+
}
|
|
1566
|
+
)
|
|
1567
|
+
|
|
1568
|
+
data = request.json
|
|
1569
|
+
max_gate_relocations = data.get("max_gate_relocations")
|
|
1570
|
+
if max_gate_relocations is not None:
|
|
1571
|
+
params.max_gate_relocations = int(max_gate_relocations)
|
|
1572
|
+
|
|
1573
|
+
params.optimize_pos_only = bool(data.get("optimize_pos_only"))
|
|
1574
|
+
params.planar_optimization = bool(data.get("planar_optimization"))
|
|
1575
|
+
|
|
1576
|
+
params.timeout = int(data.get("timeout"))
|
|
1577
|
+
|
|
1578
|
+
post_layout_optimization(layout, params)
|
|
1579
|
+
layouts[session_id] = layout # Update the layout in the session
|
|
1580
|
+
|
|
1581
|
+
layout_dimensions, gates = get_layout_information(layout)
|
|
1582
|
+
|
|
1583
|
+
return jsonify({"success": True, "layoutDimensions": layout_dimensions, "gates": gates})
|
|
1584
|
+
except RequestEntityTooLarge:
|
|
1585
|
+
raise
|
|
1586
|
+
except Exception:
|
|
1587
|
+
return _internal_error_response()
|
|
1588
|
+
|
|
1589
|
+
|
|
1590
|
+
def _crossing_type(layout, x, y):
|
|
1591
|
+
# Connected native/FGL layouts may use either layer order; actual wiring takes precedence.
|
|
1592
|
+
for z in (0, 1):
|
|
1593
|
+
incoming = layout.fanins((x, y, z))
|
|
1594
|
+
outgoing = layout.fanouts((x, y, z))
|
|
1595
|
+
if incoming and outgoing:
|
|
1596
|
+
return "bufc" if incoming[0].x == outgoing[0].x or incoming[0].y == outgoing[0].y else "bufk"
|
|
1597
|
+
|
|
1598
|
+
# Input-only crossings created here encode intent in their east-facing upper wire.
|
|
1599
|
+
for z in (1, 0):
|
|
1600
|
+
incoming = layout.fanins((x, y, z))
|
|
1601
|
+
if incoming:
|
|
1602
|
+
straight = incoming[0].x < x if z == 1 else incoming[0].y < y
|
|
1603
|
+
return "bufc" if straight else "bufk"
|
|
1604
|
+
# Fully disconnected crossings have no routing intent in native/FGL data. Foreign partial
|
|
1605
|
+
# layouts may also use a different layer convention; their original intent cannot be recovered.
|
|
1606
|
+
return "bufk"
|
|
1607
|
+
|
|
1608
|
+
|
|
1609
|
+
def get_layout_information(layout):
|
|
1610
|
+
layout_dimensions = {"x": layout.x() + 1, "y": layout.y() + 1}
|
|
1611
|
+
gates = []
|
|
1612
|
+
|
|
1613
|
+
for x in range(layout.x() + 1):
|
|
1614
|
+
for y in range(layout.y() + 1):
|
|
1615
|
+
z = _gate_layer(layout, x, y)
|
|
1616
|
+
node = layout.get_node((x, y, z))
|
|
1617
|
+
name = ""
|
|
1618
|
+
if node:
|
|
1619
|
+
if layout.is_pi(node):
|
|
1620
|
+
gate_type = "pi"
|
|
1621
|
+
try:
|
|
1622
|
+
name = layout.get_name(layout.make_signal(node))
|
|
1623
|
+
except Exception:
|
|
1624
|
+
name = ""
|
|
1625
|
+
elif layout.is_po(node):
|
|
1626
|
+
gate_type = "po"
|
|
1627
|
+
try:
|
|
1628
|
+
name = layout.get_name(layout.make_signal(node))
|
|
1629
|
+
except Exception:
|
|
1630
|
+
name = ""
|
|
1631
|
+
elif layout.is_wire(node):
|
|
1632
|
+
gate_type = "buf"
|
|
1633
|
+
above_gate = layout.above(layout.get_tile(node))
|
|
1634
|
+
if z == 0 and layout.z() == 1 and not layout.is_empty_tile(above_gate):
|
|
1635
|
+
gate_type = _crossing_type(layout, x, y)
|
|
1636
|
+
if layout.fanout_size(node) == 2:
|
|
1637
|
+
gate_type = "fanout"
|
|
1638
|
+
elif layout.is_inv(node):
|
|
1639
|
+
gate_type = "inv"
|
|
1640
|
+
elif layout.is_and(node):
|
|
1641
|
+
gate_type = "and"
|
|
1642
|
+
elif layout.is_nand(node):
|
|
1643
|
+
gate_type = "nand"
|
|
1644
|
+
elif layout.is_or(node):
|
|
1645
|
+
gate_type = "or"
|
|
1646
|
+
elif layout.is_nor(node):
|
|
1647
|
+
gate_type = "nor"
|
|
1648
|
+
elif layout.is_xor(node):
|
|
1649
|
+
gate_type = "xor"
|
|
1650
|
+
elif layout.is_xnor(node):
|
|
1651
|
+
gate_type = "xnor"
|
|
1652
|
+
else:
|
|
1653
|
+
raise Exception("Unsupported gate type")
|
|
1654
|
+
|
|
1655
|
+
gate_info = {
|
|
1656
|
+
"x": x,
|
|
1657
|
+
"y": y,
|
|
1658
|
+
"type": gate_type,
|
|
1659
|
+
"connections": [],
|
|
1660
|
+
"name": name,
|
|
1661
|
+
}
|
|
1662
|
+
# Get fanins (source nodes)
|
|
1663
|
+
fanins = layout.fanins((x, y, z))
|
|
1664
|
+
for fin in fanins:
|
|
1665
|
+
gate_info["connections"].append({"sourceX": fin.x, "sourceY": fin.y})
|
|
1666
|
+
if gate_type in ("bufc", "bufk"):
|
|
1667
|
+
fanins = layout.fanins((x, y, 1))
|
|
1668
|
+
for fin in fanins:
|
|
1669
|
+
gate_info["connections"].append({"sourceX": fin.x, "sourceY": fin.y})
|
|
1670
|
+
gates.append(gate_info)
|
|
1671
|
+
return layout_dimensions, gates
|
|
1672
|
+
|
|
1673
|
+
|
|
1674
|
+
def start_server(host: str = "127.0.0.1", port: int = 5001, open_browser: bool = True) -> None:
|
|
1675
|
+
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
|
1676
|
+
url_host = f"[{host}]" if ":" in host else host
|
|
1677
|
+
url = f"http://{url_host}:{port}"
|
|
1678
|
+
print(
|
|
1679
|
+
f"Server is hosted at: {url}.",
|
|
1680
|
+
"To stop it, interrupt the process (e.g., via CTRL+C). \n",
|
|
1681
|
+
)
|
|
1682
|
+
cli.show_server_banner = lambda *_args: None
|
|
1683
|
+
if open_browser:
|
|
1684
|
+
webbrowser.open(url)
|
|
1685
|
+
app.run(debug=False, host=host, port=port)
|
|
1686
|
+
|
|
1687
|
+
|
|
1688
|
+
def main(argv: Sequence[str] | None = None) -> None:
|
|
1689
|
+
parser = argparse.ArgumentParser(description="Start the MNT Designer web interface.")
|
|
1690
|
+
parser.add_argument("--host", default="127.0.0.1", help="Address to bind to (default: 127.0.0.1).")
|
|
1691
|
+
parser.add_argument("--port", type=int, default=5001, help="Port to listen on (default: 5001).")
|
|
1692
|
+
parser.add_argument("--no-browser", action="store_true", help="Do not open a browser automatically.")
|
|
1693
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {version('mnt.designer')}")
|
|
1694
|
+
args = parser.parse_args(argv)
|
|
1695
|
+
if not 1 <= args.port <= 65535:
|
|
1696
|
+
parser.error("--port must be between 1 and 65535")
|
|
1697
|
+
start_server(host=args.host, port=args.port, open_browser=not args.no_browser)
|
|
1698
|
+
|
|
1699
|
+
|
|
1700
|
+
if __name__ == "__main__":
|
|
1701
|
+
main()
|