Skip to content

Commit 67b80c4

Browse files
committed
Add combinatorial example generator to utils
Signed-off-by: Krzysztof Boronski <[email protected]>
1 parent 297f30f commit 67b80c4

File tree

1 file changed

+296
-0
lines changed

1 file changed

+296
-0
lines changed

utils/generate_design.py

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright 2018-2022 F4PGA Authors
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# SPDX-License-Identifier: Apache-2.0
19+
20+
from copy import copy
21+
import random
22+
import networkx as nx
23+
from argparse import ArgumentParser
24+
25+
26+
def lut_param_randomizer(len):
27+
r = ['0', '1']
28+
29+
init = f'{len}\'b'
30+
for _ in range(0, len):
31+
init += random.choice(r)
32+
33+
return {'INIT': init}
34+
35+
36+
class Cell:
37+
name: str
38+
inputs: 'list[str]'
39+
outputs: 'list[str]'
40+
41+
def __init__(
42+
self,
43+
name: str,
44+
inputs: 'list[str]',
45+
outputs: 'list[str]',
46+
):
47+
self.name = name
48+
self.inputs = inputs
49+
self.outputs = outputs
50+
51+
52+
class PlaceableCell:
53+
cell: Cell
54+
probability: float
55+
param_randomizer: None
56+
57+
def __init__(
58+
self,
59+
name: str,
60+
inputs: 'list[str]',
61+
outputs: 'list[str]',
62+
probability: float,
63+
param_randomizer=None
64+
):
65+
self.cell = Cell(name, inputs, outputs)
66+
self.probability = probability
67+
self.param_randomizer = param_randomizer
68+
69+
70+
placeables = [
71+
PlaceableCell(
72+
name='LUT4',
73+
inputs=['I0', 'I1', 'I2', 'I3'],
74+
outputs=['O'],
75+
probability=1.0,
76+
param_randomizer=lambda: lut_param_randomizer(16)
77+
),
78+
PlaceableCell(
79+
name='LUT5',
80+
inputs=['I0', 'I1', 'I2', 'I3', 'I4'],
81+
outputs=['O'],
82+
probability=1.0,
83+
param_randomizer=lambda: lut_param_randomizer(32)
84+
),
85+
PlaceableCell(
86+
name='LUT6',
87+
inputs=['I0', 'I1', 'I2', 'I3', 'I4', 'I5'],
88+
outputs=['O'],
89+
probability=1.0,
90+
param_randomizer=lambda: lut_param_randomizer(64)
91+
)
92+
]
93+
94+
io_cells = {
95+
'in':
96+
{
97+
'cell': Cell(name='IBUF', inputs=['I'], outputs=['O']),
98+
'i': 'I',
99+
'o': 'O',
100+
'params': {}
101+
},
102+
'out':
103+
{
104+
'cell': Cell(name='OBUF', inputs=['I'], outputs=['O']),
105+
'i': 'I',
106+
'o': 'O',
107+
'params': {}
108+
}
109+
}
110+
111+
total_placeable_weight = 0.0
112+
for p in placeables:
113+
total_placeable_weight += p.probability
114+
115+
116+
def random_cell() -> PlaceableCell:
117+
global total_placeable_weight
118+
119+
v = random.uniform(0.0, total_placeable_weight)
120+
121+
acc = 0.0
122+
for c in placeables:
123+
if (v > acc) and (v <= acc + c.probability):
124+
return c
125+
else:
126+
acc += c.probability
127+
128+
raise RuntimeError('Random value out-of-range')
129+
130+
131+
class Netlist:
132+
cell_type_count: 'dict[str, int]'
133+
free_inpins: 'set[str]'
134+
free_outpins: 'set[str]'
135+
g: 'nx.DiGraph'
136+
cells: 'list[tuple[str, str, dict[str, str]]]'
137+
ports: 'list[str]'
138+
139+
def __init__(self):
140+
self.cell_type_count = {}
141+
self.g = nx.DiGraph()
142+
self.free_inpins = set()
143+
self.free_outpins = set()
144+
self.cells = []
145+
self.ports = []
146+
147+
def name_cell(self, c: Cell) -> str:
148+
if self.cell_type_count.get(c.name) is None:
149+
self.cell_type_count[c.name] = 0
150+
ccount = self.cell_type_count[c.name]
151+
self.cell_type_count[c.name] += 1
152+
return f'{c.name}_{ccount}'
153+
154+
def add_cell(self, cell: Cell, **properties: 'str') -> str:
155+
cell_name = self.name_cell(cell)
156+
self.cells.append((cell, cell_name, properties))
157+
158+
for input in cell.inputs:
159+
name = f'{cell_name}/{input}'
160+
self.g.add_node(name, cell_instance=cell_name)
161+
self.free_inpins.add(name)
162+
for output in cell.outputs:
163+
name = f'{cell_name}/{output}'
164+
self.g.add_node(name, cell_instance=cell_name)
165+
self.free_outpins.add(name)
166+
167+
return cell_name
168+
169+
def add_port(self, cell_pin: str, dir: str, name: str):
170+
self.ports.append(name)
171+
self.g.add_node(name)
172+
173+
if dir.lower() == 'in':
174+
self.g.nodes[name]['net'] = f'net_{name}'
175+
self.connect_driver_sink(name, cell_pin)
176+
elif dir.lower() == 'out':
177+
self.g.nodes[cell_pin]['net'] = f'net_{name}'
178+
self.connect_driver_sink(cell_pin, name)
179+
else:
180+
raise RuntimeError(f'Incorrect pin direction `{dir}`')
181+
182+
def connect_driver_sink(self, driver: str, sink: str):
183+
net = self.g.nodes.data('net', default=None)[driver]
184+
net_name = f'net_{driver}'
185+
if net is None:
186+
self.g.nodes[driver]['net'] = net_name
187+
self.g.nodes[sink]['net'] = net_name
188+
189+
self.g.add_edge(driver, sink)
190+
if sink in self.free_inpins:
191+
self.free_inpins.remove(sink)
192+
193+
def export_tcl(self) -> str:
194+
tcl = ''
195+
196+
for port in self.ports:
197+
dir = 'IN' if len(self.g.out_edges(port)) > 0 else 'OUT'
198+
tcl += f'create_port -direction {dir} {port}\n'
199+
200+
tcl += '\n'
201+
202+
for (cell, cell_name, properties) in self.cells:
203+
tcl += f'create_cell -ref {cell.name} {cell_name}\n'
204+
for (prop_name, prop_value) in properties.items():
205+
tcl += f'set_property {prop_name} ' + '{' + prop_value + '}' + f' [get_cell {cell_name}]\n'
206+
207+
tcl += '\n'
208+
209+
nets = {}
210+
for (driver, sink) in self.g.edges:
211+
net = self.g.nodes[driver]['net']
212+
if net not in nets:
213+
tcl += f'create_net {net.replace("/", "__")}\n'
214+
nets[net] = set()
215+
216+
nets[net].add(driver)
217+
nets[net].add(sink)
218+
219+
tcl += '\n'
220+
221+
for (net, objects) in nets.items():
222+
tcl += f'connect_net -net {net.replace("/", "__")} -objects ' + '{' + ' '.join(
223+
objects
224+
) + '}\n'
225+
226+
return tcl
227+
228+
229+
def add_and_connect_cells(netlist: Netlist, no_of_cells: int):
230+
# Add random cells
231+
for _ in range(0, no_of_cells):
232+
cell = random_cell()
233+
properties = cell.param_randomizer(
234+
) if cell.param_randomizer is not None else {}
235+
netlist.add_cell(cell.cell, **properties)
236+
237+
# Make random connections
238+
free_outpins = list(netlist.free_outpins)
239+
for sink in copy(netlist.free_inpins):
240+
if len(netlist.g.in_edges(sink)) != 0:
241+
continue
242+
driver = random.choice(free_outpins)
243+
netlist.connect_driver_sink(driver, sink)
244+
245+
246+
def add_io(netlist: Netlist, input_cnt: int, output_cnt: int):
247+
global io_cells
248+
249+
in_def = io_cells['in']
250+
out_def = io_cells['out']
251+
252+
for i in range(0, input_cnt):
253+
name = netlist.add_cell(in_def['cell'], **in_def['params'])
254+
netlist.add_port(f'{name}/{in_def["i"]}', 'IN', f'in{i}')
255+
for i in range(0, output_cnt):
256+
name = netlist.add_cell(out_def['cell'], **out_def['params'])
257+
outpin = f'{name}/{out_def["o"]}'
258+
netlist.add_port(outpin, 'OUT', f'out{i}')
259+
netlist.free_outpins.remove(outpin)
260+
261+
262+
def main():
263+
parser = ArgumentParser()
264+
parser.add_argument(
265+
'--cell-count',
266+
'-c',
267+
type=int,
268+
default=10,
269+
help='Number of cells to place'
270+
)
271+
parser.add_argument(
272+
'--input-count',
273+
'-I',
274+
type=int,
275+
default=8,
276+
help='Number of inputs to add'
277+
)
278+
parser.add_argument(
279+
'--output-count',
280+
'-O',
281+
type=int,
282+
default=8,
283+
help='Number of outputs to add'
284+
)
285+
286+
args = parser.parse_args()
287+
288+
netlist = Netlist()
289+
add_io(netlist, args.input_count, args.output_count)
290+
add_and_connect_cells(netlist, args.cell_count)
291+
292+
print(netlist.export_tcl())
293+
294+
295+
if __name__ == '__main__':
296+
main()

0 commit comments

Comments
 (0)