├── .gitignore
├── README.md
├── __main__.py
├── barcas.py
├── circuit.py
├── circuiterror.py
├── demo.py
├── images
└── decision_tree_method.png
├── ml_algorithms
└── decision_tree.py
├── netlist.py
├── poisonoak.config
├── poisonoak.help
├── pruning_algorithms
├── ccarving.py
├── glpsignificance.py
├── inouts.py
└── probprun.py
├── synthesis.py
├── technology.py
├── templates
├── NanGate15nm.lib
├── NanGate15nm.v
├── resynth.ys
├── stat.ys
└── synth.ys
├── test.py
└── utils.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Cloned benchmarks
2 | ALS-benchmark-circuits/
3 |
4 | # Images of circuits
5 | *.png
6 |
7 | # Byte-compiled / optimized / DLL files
8 | __pycache__/
9 | *.py[cod]
10 | *$py.class
11 |
12 | # C extensions
13 | *.so
14 |
15 | # Distribution / packaging
16 | .Python
17 | build/
18 | develop-eggs/
19 | dist/
20 | downloads/
21 | eggs/
22 | .eggs/
23 | lib/
24 | lib64/
25 | parts/
26 | sdist/
27 | var/
28 | wheels/
29 | pip-wheel-metadata/
30 | share/python-wheels/
31 | *.egg-info/
32 | .installed.cfg
33 | *.egg
34 | MANIFEST
35 |
36 | # PyInstaller
37 | # Usually these files are written by a python script from a template
38 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
39 | *.manifest
40 | *.spec
41 |
42 | # Installer logs
43 | pip-log.txt
44 | pip-delete-this-directory.txt
45 |
46 | # Unit test / coverage reports
47 | htmlcov/
48 | .tox/
49 | .nox/
50 | .coverage
51 | .coverage.*
52 | .cache
53 | nosetests.xml
54 | coverage.xml
55 | *.cover
56 | *.py,cover
57 | .hypothesis/
58 | .pytest_cache/
59 |
60 | # Translations
61 | *.mo
62 | *.pot
63 |
64 | # Django stuff:
65 | *.log
66 | local_settings.py
67 | db.sqlite3
68 | db.sqlite3-journal
69 |
70 | # Flask stuff:
71 | instance/
72 | .webassets-cache
73 |
74 | # Scrapy stuff:
75 | .scrapy
76 |
77 | # Sphinx documentation
78 | docs/_build/
79 |
80 | # PyBuilder
81 | target/
82 |
83 | # Jupyter Notebook
84 | .ipynb_checkpoints
85 |
86 | # IPython
87 | profile_default/
88 | ipython_config.py
89 |
90 | # pyenv
91 | .python-version
92 |
93 | # pipenv
94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
97 | # install all needed dependencies.
98 | #Pipfile.lock
99 |
100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
101 | __pypackages__/
102 |
103 | # Celery stuff
104 | celerybeat-schedule
105 | celerybeat.pid
106 |
107 | # SageMath parsed files
108 | *.sage.py
109 |
110 | # Environments
111 | .env
112 | .venv
113 | env/
114 | venv/
115 | ENV/
116 | env.bak/
117 | venv.bak/
118 |
119 | # Spyder project settings
120 | .spyderproject
121 | .spyproject
122 |
123 | # Rope project settings
124 | .ropeproject
125 |
126 | # mkdocs documentation
127 | /site
128 |
129 | # mypy
130 | .mypy_cache/
131 | .dmypy.json
132 | dmypy.json
133 |
134 | # Pyre type checker
135 | .pyre/
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AxLS
2 |
3 | ### **An Open-Source Framework for Netlist Transformation Approximate Logic Synthesis**
4 |
5 | #### Authors
6 | Jorge Castro-Godínez, professor, Tecnológico de Costa Rica
7 |
8 | Humberto Barrantes-García, student, Tecnológico de Costa Rica
9 |
10 | Roger Morales-Monge, student, Tecnológico de Costa Rica
11 |
12 | ## Table of Contents
13 |
14 | 1. [AxLS](#axls)
15 | 2. [Authors](#authors)
16 | 3. [Requirements](#requirements)
17 | 1. [Installing Yosys](#installing-yosys)
18 | 2. [Installing Icarus Verilog](#installing-icarus-verilog)
19 | 3. [Cloning benchmarks](#cloning-benchmarks)
20 | 4. [Executing Demo](#executing-demo)
21 | 5. [Using AxLS](#using-axls)
22 | 1. [Parsing a netlist](#parsing-a-netlist)
23 | 2. [Deleting a node](#deleting-a-node)
24 | 3. [Simulation and Error Estimation](#simulation-and-error-estimation)
25 | 6. [ALS Algorithms](#als-algorithms)
26 | 1. [Pruning Algorithms](#pruning-algorithms)
27 | - [InOuts](#inouts)
28 | - [Pseudo-Probabilistic Pruning (ProbPrun)](#pseudo-probabilistic-pruning-probprun)
29 | 2. [ML Supervised Learning](#ml-supervised-learning)
30 | - [Decision Tree (DT)](#decision-tree-dt)
31 | 7. [Files and Folders](#files-and-folders)
32 |
33 | ## Requirements
34 |
35 | To use AxLS, Python, Yosys, and Icarus Verilog are required, at least in the
36 | following versions:
37 |
38 | | name | version | - |
39 | | -------------- | ------- | ---- |
40 | | Icarus Verilog | 10.3 | |
41 | | Yosys | 0.9+932 | |
42 | | Python | 3.6.8 | |
43 |
44 |
45 |
46 | ### Installing Yosys
47 |
48 | ```bash
49 | sudo apt-get install build-essential clang bison flex \
50 | libreadline-dev gawk tcl-dev libffi-dev git \
51 | graphviz xdot pkg-config python3 libboost-system-dev \
52 | libboost-python-dev libboost-filesystem-dev zlib1g-dev
53 |
54 | git clone https://github.com/cliffordwolf/yosys.git
55 | cd yosys/
56 | make config-clang
57 | make config-gcc
58 | make
59 | make test #optional
60 | sudo make install
61 | ```
62 |
63 |
64 |
65 | ### Installing Icarus Verilog
66 |
67 | ```bash
68 | wget ftp://ftp.icarus.com/pub/eda/verilog/v10/verilog-10.3.tar.gz
69 | tar -zxvf verilog-10.3.tar.gz
70 | cd verilog-10.3/
71 | ./configure
72 | make
73 | sudo make install
74 | ```
75 |
76 | ### Cloning benchmarks
77 |
78 | You'll need to clone the benchmarks from the [ALS-benchmark-circuits](https://github.com/ECASLab/ALS-benchmark-circuits) repo.
79 |
80 | ```sh
81 | git clone https://github.com/ECASLab/ALS-benchmark-circuits --depth=1
82 | ```
83 |
84 | ## Executing Demo
85 |
86 | After cloning the benchmarks and installing the required dependencies you can
87 | execute the `demo.py`.
88 |
89 | Note that the demo requires the python `graphviz` package because it renders
90 | images of the circuit graph. You can skip this by modifying the file and setting
91 | the `CREATE_IMAGE` constant to `False.
92 |
93 | Example installation with `pip`:
94 | ```sh
95 | pip install graphviz
96 | ```
97 |
98 | Demo execution:
99 | ```sh
100 | python demo.py
101 | ```
102 |
103 | #### Sample output
104 |
105 | The demo prints a lot of output, including
106 |
107 | - Progress reports of simulations:
108 |
109 | ```
110 | -- Progress: 99995/100000 --
111 | -- Progress: 99996/100000 --
112 | -- Progress: 99997/100000 --
113 | -- Progress: 99998/100000 --
114 | -- Progress: 99999/100000 --
115 | -- Progress: 100000/100000 --
116 | ```
117 |
118 | - The circuit's XML:
119 | ```
120 | Showing circuit XML
121 | ---------------------
122 | b''
125 | ---------------------
126 | Press any key to continue...
127 | ```
128 |
129 | - The circuit graph representation as an image, including the original circuit and with a node set for deletion.
130 |
131 | - Some circuit properties:
132 |
133 | ```
134 | Circuit inputs...
135 | ['X[0]', 'X[1]', 'X[2]', 'X[3]', 'X[4]', 'X[5]', 'X[6]', 'X[7]', 'X[8]', 'X[9]
136 | ', 'X[10]', 'X[11]', 'X[12]', 'X[13]', 'X[14]', 'X[15]', 'Y[0]', 'Y[1]', 'Y[2]
137 | ', 'Y[3]', 'Y[4]', 'Y[5]', 'Y[6]', 'Y[7]', 'Y[8]', 'Y[9]', 'Y[10]', 'Y[11]', '
138 | Y[12]', 'Y[13]', 'Y[14]', 'Y[15]']
139 | Circuit outputs...
140 | ['S[0]', 'S[1]', 'S[2]', 'S[3]', 'S[4]', 'S[5]', 'S[6]', 'S[7]', 'S[8]', 'S[9]
141 | ', 'S[10]', 'S[11]', 'S[12]', 'S[13]', 'S[14]', 'S[15]', 'S[16]']
142 | Root of XML tree:
143 | ```
144 |
145 | - Sample of what the InOuts method would suggest:
146 |
147 | ```
148 | Nodes to delete if input 0 is constant
149 | ['_109_', '_129_']
150 | Nodes to delete if input 3 is constant
151 | ['_109_', '_129_', '_108_', '_110_', '_112_', '_111_', '_150_', '_106_', '_107_
152 | ', '_113_', '_114_', '_149_', '_115_', '_104_', '_105_', '_116_', '_147_', '_14
153 | 8_']
154 | Nodes to delete if output 0 is constant
155 | ['_129_']
156 | Nodes to delete if output 5 is constant
157 | ['_145_', '_144_']
158 | ```
159 |
160 | - Sample of what the Pseudo ProbPrun method would suggest:
161 | ```
162 | ProbPrun suggest delete the node _068_ because it's 0 75% of the time
163 | _069_ is 0 75% of the time
164 | _070_ is 1 75% of the time
165 | _073_ is 1 75% of the time
166 | _075_ is 0 75% of the time
167 | _076_ is 1 75% of the time
168 | _079_ is 1 75% of the time
169 | _085_ is 1 75% of the time
170 | _086_ is 0 75% of the time
171 | _093_ is 0 75% of the time
172 | _096_ is 1 75% of the time
173 | ```
174 |
175 | - Final error of approximate circuit:
176 | ```
177 | Mean Error Distance of approximate circuit with node _101_ deleted: 3.979
178 | ```
179 |
180 | ## Using AxLS
181 |
182 | ### Parsing a netlist
183 |
184 | 1. First, import the `Circuit` class:
185 |
186 | ```python
187 | from circuit import Circuit
188 | ```
189 |
190 | 2. We need the path to the verilog and saif files to create our `Circuit` object, we can define them with constants:
191 |
192 | ```python
193 | # verilog file of the circuit we want to approximate
194 | RTL = "ALS-benchmark-circuits/BK_16b/BK_16b.v"
195 |
196 | # [optional] a saif for the circuit we want to approximate
197 | SAIF = "ALS-benchmark-circuits/BK_16b/NanGate15nm/BK_16b.saif"
198 | ```
199 |
200 | 3. When creating a `Circuit` object, the library will parse the provided files and build an XML tree with all the relevant information of the circuit:
201 |
202 | ```python
203 | # Circuit creates a representation of the circuit using python objects
204 | our_circuit = Circuit(RTL, "NanGate15nm", SAIF)
205 | ```
206 |
207 | 4. You can print the circuit from the XML file, by calling the `get_circuit_xml()` function:
208 |
209 | ```python
210 | print(our_circuit.get_circuit_xml())
211 | ```
212 |
213 | 5. Or you can also print the circuit as a graph with `show()`
214 |
215 | ```python
216 | our_circuit.show()
217 | ```
218 |
219 | 6. From `our_circuit`, you can also obtain the circuit inputs/outputs
220 |
221 | ```python
222 | print("Circuit inputs...")
223 | print(our_circuit.inputs)
224 | print("Circuit outputs...")
225 | print(our_circuit.outputs)
226 | ```
227 |
228 | That will return something like:
229 |
230 | ```
231 | Circuit inputs...
232 | ['X[0]', 'X[1]', 'X[2]', 'X[3]', 'X[4]', 'X[5]', 'X[6]', 'X[7]', 'X[8]', 'X[9]', 'X[10]', 'X[11]', 'X[12]', 'X[13]', 'X[14]', 'X[15]', 'Y[0]', 'Y[1]', 'Y[2]', 'Y[3]', 'Y[4]', 'Y[5]', 'Y[6]', 'Y[7]', 'Y[8]', 'Y[9]', 'Y[10]', 'Y[11]', 'Y[12]', 'Y[13]', 'Y[14]', 'Y[15]']
233 | Circuit outputs...
234 | ['S[0]', 'S[1]', 'S[2]', 'S[3]', 'S[4]', 'S[5]', 'S[6]', 'S[7]', 'S[8]', 'S[9]', 'S[10]', 'S[11]', 'S[12]', 'S[13]', 'S[14]', 'S[15]', 'S[16]']
235 | ```
236 |
237 | 7. Remember, the circuit is represented as an XML (ElementTree) so if you want to iterate over the XML just get the root of the tree:
238 |
239 | ```python
240 | print(our_circuit.netl_root)
241 | ```
242 |
243 | ```
244 |
245 | ```
246 |
247 | Using this node you can implement your own pruning algorithms. Because ElementTree allows you to search XML nodes based on their attributes using xpath syntax.
248 |
249 | > Don't Reinvent the Wheel!
250 |
251 |
252 |
253 | ### Deleting a node
254 |
255 | 1. The first example method we provide to delete nodes is quite simple, just delete a node based on its name. You can do it in two different ways:
256 |
257 | ```python
258 | # Using ElementTree xpath syntax
259 | node101 = our_circuit.netl_root.find("./node[@var='_101_']")
260 | node101.set("delete", "yes")
261 | ```
262 |
263 | Or
264 |
265 | ```python
266 | # Using the built-in functionality
267 | our_circuit.delete("_101_")
268 | ```
269 |
270 | When you set the attribute `delete` of a node to `yes`, it means that this node will be deleted the next time our circuit is saved in the filesystem. **The node will remain in the xml tree!** (just in case we need to revert a deletion).
271 |
272 | ### Simulation and Error Estimation
273 |
274 | Simulation stage and error estimation are executed inside one method called `simulate_and_compute_error`. But first, in order to execute a simulation and calculate its error you need to provide:
275 |
276 | - A testbench.
277 | - A dataset for the testbench to use.
278 | - The exact results.
279 | - The name of the approximated results file.
280 | - Error metric to use.
281 |
282 | 1. Let's start generating a dataset that we'll use in our simulations:
283 |
284 | ```python
285 | # Use 10_000 input samples of the circuit. You'll want a larger or smaller
286 | # sample size based on the circuit inputs size.
287 | # For example, this one has 32 bits, 2^32 which gives input possibilities
288 | # (~4 billion). So let's sample around 1% of those input possibilities with 40
289 | # million samples.
290 |
291 | SAMPLES=40_000_000
292 | DATASET = "ALS-benchmark-circuits/BK_16b/dataset"
293 |
294 | our_circuit.generate_dataset(DATASET, SAMPLES)
295 | ```
296 |
297 | 2. Now we can generate a testbench that will rely on the dataset:
298 |
299 | ```python
300 | # The path where the output of this simulation will be created
301 | TB = "ALS-benchmark-circuits/BK_16b/BK_16b_tb.v"
302 | our_circuit.write_tb(TB, DATASET, iterations=SAMPLES)
303 | ```
304 |
305 | 3. We can generate an exact output of our circuit with the `exact_output` method:
306 |
307 | ```python
308 | EXACT_RESULT = "ALS-benchmark-circuits/BK_16b/output_exact.txt"
309 | our_circuit.exact_output(TB, EXACT_RESULT)
310 | ```
311 |
312 | 4. Now we are ready to execute the simulation of our approximate circuit. We pass in the `"med"` argument as the error metric to be used. In this case Mean Error Distance.
313 |
314 | ```python
315 | # The path where the output of this simulation will be created
316 | APPROX_RESULT = "ALS-benchmark-circuits/BK_16b/output_approx.txt"
317 |
318 | error = our_circuit.simulate_and_compute_error(TB, EXACT_RESULT, APPROX_RESULT, "med")
319 | ```
320 |
321 | This should returns a number like the following:
322 |
323 | ```
324 | 63.011
325 | ```
326 |
327 | ## ALS Algorithms
328 |
329 | This framework currently provides 2 kinds of ALS algorithms:
330 |
331 | - Pruning algorithms
332 | - ML Supervised Learning algorithms
333 |
334 | ### Pruning Algorithms
335 |
336 | These algorithms suggest which nodes to delete based on circuit data or
337 | heuristics.
338 |
339 | TODO: Missing documentation on `ccarving` and `glpsignificance`
340 |
341 | #### InOuts
342 |
343 | Suggest which nodes to delete if the inputs or the outputs are constants.
344 |
345 | 1. Lets start with InOuts methods. Import both `GetInputs` and `GetOutputs`
346 |
347 | ```python
348 | from pruning_algorithms.inouts import GetInputs, GetOutputs
349 | ```
350 |
351 | 2. `GetInputs` will give you a list of nodes that can be deleted if the inputs specified are constants:
352 |
353 | ```python
354 | # Extracts the nodes that can be deleted if inputs of bit 0 are constants
355 | inputs = ["X[0]","Y[0]"]
356 | depricable_nodes = GetInputs(our_circuit.netl_root, inputs)
357 | print(depricable_nodes)
358 | print("Nodes to delete if input 0 is constant")
359 | print([ n.attrib["var"] for n in depricable_nodes ])
360 | ```
361 |
362 | Shows:
363 |
364 | ```
365 | Nodes to delete if input 0 is constant
366 | ['_069_', '_147_']
367 | ```
368 |
369 | Other example:
370 |
371 | ```python
372 | # Extracts the nodes that can be deleted if inputs of bit 3 are constants
373 | inputs = ["X[0]","Y[0]","X[1]","Y[1]","X[2]","Y[2]","X[3]","Y[3]"]
374 | depricable_nodes = GetInputs(our_circuit.netl_root, inputs)
375 | print("Nodes to delete if input 3 is constant")
376 | print([ n.attrib["var"] for n in depricable_nodes ])
377 | ```
378 |
379 | Shows:
380 |
381 | ```
382 | Nodes to delete if input 3 is constant
383 | ['_069_', '_147_', '_066_', '_067_', '_075_', '_068_', '_070_', '_071_', '_072_', '_073_', '_074_', '_076_', '_080_', '_077_', '_078_', '_086_', '_079_', '_081_']
384 | ```
385 |
386 | 3. `GetOutputs` will give you a list of nodes that can be deleted if the outputs specified are constants:
387 |
388 | ```python
389 | # Extracts the nodes that can be deleted if output of bit 0 is constant
390 | outputs = ["S[0]"]
391 | depricable_nodes = GetOutputs(our_circuit.netl_root, outputs)
392 | print(depricable_nodes)
393 | print("Nodes to delete if output 0 is constant")
394 | print([ n.attrib["var"] for n in depricable_nodes ])
395 | ```
396 |
397 | Shows:
398 |
399 | ```
400 | Nodes to delete if output 0 is constant
401 | ['_147_']
402 | ```
403 |
404 | Other example:
405 |
406 | ```python
407 | # Extracts the nodes that can be deleted if outputs of bit 3 is constant
408 | outputs = ["S[5]"]
409 | depricable_nodes = GetOutputs(our_circuit.netl_root, outputs)
410 | print("Nodes to delete if output 5 is constant")
411 | print([ n.attrib["var"] for n in depricable_nodes ])
412 | ```
413 |
414 | Shows:
415 |
416 | ```
417 | Nodes to delete if output 5 is constant
418 | ['_091_']
419 | ```
420 |
421 | #### Pseudo-Probabilistic Pruning (ProbPrun)
422 |
423 | Suggests nodes to delete based on the toggling time a specific node keep a constant value (1 or 0) in their output.
424 |
425 | Similar as presented in
426 |
427 | > J. Schlachter, V. Camus, K. V. Palem and C. Enz, "Design and Applications of Approximate Circuits by Gate-Level Pruning," in IEEE Transactions on Very Large Scale Integration (VLSI) Systems, vol. 25, no. 5, pp. 1694-1702, May 2017, doi: 10.1109/TVLSI.2017.2657799.
428 |
429 | 1. In order to use ProbPrun methods **make sure you specified a SAIF file when you created the Circuit object**. First lets import the method:
430 |
431 | ```python
432 | from pruning_algorithms.probprun import GetOneNode
433 | ```
434 |
435 | 2. `GetOneMethod` is a generator, so it will return one node every time you call it, so lets first create it:
436 |
437 | ```python
438 | pseudo_probprun = GetOneNode(our_circuit.netl_root)
439 | ```
440 |
441 | 3. Now we can call it, every time it retrieves the node to delete, the logic value it has most of the time, and how much time it keeps that value:
442 |
443 | ```python
444 | node, output, time = next(pseudo_probprun)
445 | print(f"ProbPrun suggest delete the node {node} because it's {output} {time}% of the time")
446 | ```
447 |
448 | This should show:
449 |
450 | ```
451 | ProbPrun suggest delete the node _114_ because it's 0 100% of the time
452 | ```
453 |
454 | 4. As any generator, you can use it in for loops:
455 |
456 | ```python
457 | for x in range (30):
458 | node, output, time = next(pseudo_probprun)
459 | print(f"{node} is {output} {time}% of the time")
460 | ```
461 |
462 | This will return:
463 |
464 | ```
465 | ProbPrun suggest delete the node _114_ because is 0 100% of the time
466 | _115_ is 1 100% of the time
467 | _116_ is 0 100% of the time
468 | _117_ is 1 100% of the time
469 | _120_ is 1 100% of the time
470 | _121_ is 1 100% of the time
471 | _122_ is 0 100% of the time
472 | _123_ is 1 100% of the time
473 | _125_ is 0 100% of the time
474 | _126_ is 1 100% of the time
475 | _127_ is 0 100% of the time
476 | _128_ is 1 100% of the time
477 | _129_ is 0 100% of the time
478 | _131_ is 1 100% of the time
479 | _132_ is 1 100% of the time
480 | _133_ is 0 100% of the time
481 | _134_ is 1 100% of the time
482 | _136_ is 0 100% of the time
483 | _137_ is 1 100% of the time
484 | _138_ is 0 100% of the time
485 | _139_ is 1 100% of the time
486 | _140_ is 0 100% of the time
487 | _142_ is 1 100% of the time
488 | _143_ is 1 100% of the time
489 | _144_ is 0 100% of the time
490 | _145_ is 1 100% of the time
491 | _066_ is 1 75% of the time
492 | _071_ is 0 75% of the time
493 | _072_ is 1 75% of the time
494 | _077_ is 1 75% of the time
495 | _082_ is 0 75% of the tim
496 | ```
497 |
498 | ### ML Supervised Learning
499 |
500 | These algorithms train an ML model based on a circuit's inputs and outputs in
501 | order to learn a generalized version of the boolean function, then maps the
502 | model into an approximate circuit, fully replacing the original circuit.
503 |
504 | #### Decision Tree (DT)
505 |
506 | This method works by training a DT on the input and output data of
507 | a real circuit. Then synthesizing that DT into a verilog circuit.
508 |
509 | Note that this method requires installing the `scikit-learn` package, since it
510 | leverages its DT implementation.
511 |
512 | The following diagram gives a simplified view of how the method operates:
513 |
514 |
515 |
516 |
517 |
518 | 1. Import the `DecisionTreeCircuit` class, which wraps logic to train and
519 | convert scikit-learn DTs into Boolean circuits.
520 |
521 | ```python
522 | from ml_algorithms.decision_tree import DecisionTreeCircuit
523 | ```
524 |
525 | 2. Import the input and output datasets. For this we can use `read_dataset`.
526 |
527 | ```python
528 | from utils import read_dataset
529 |
530 | # Example with RCA_4b benchmark
531 | NAME = "RCA_4b"
532 | INPUT = f"ALS-benchmark-circuits/{NAME}/dataset"
533 | ORIGINAL_OUTPUT = f"ALS-benchmark-circuits/{NAME}/output0.txt"
534 | DATASET_SIZE = 1000
535 |
536 | # We use base 16 because input datasets are generated in hexadecimal by default
537 | inputs = read_dataset(INPUT, 16, DATASET_SIZE)
538 | # We use base 10 because a testbench outputs are written in decimal
539 | outputs = read_dataset(ORIGINAL_OUTPUT, 10, DATASET_SIZE)
540 | ```
541 |
542 | If we need to generate the datasets first, we can do this with the `Circuit.generate_dataset()` and `Circuit.exact_output()` methods:
543 |
544 | ```python
545 | TB = f"ALS-benchmark-circuits/{NAME}/{NAME}_tb.v"
546 |
547 | original_circuit.generate_dataset(INPUT, DATASET_SIZE)
548 | original_circuit.exact_output(TB, ORIGINAL_OUTPUT)
549 | ```
550 |
551 | 3. Train the DT model:
552 |
553 | ```python
554 | clf = DecisionTreeCircuit(original_circuit.inputs, original_circuit.outputs, max_depth=4)
555 | clf.train(inputs, outputs)
556 | ```
557 |
558 | `max_depth` controls tree complexity. You can also pass any valid [sklearn.tree.DecisionTreeClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html) kwargs.
559 |
560 | 4. Synthesize the approximate circuit from the trained DT model:
561 |
562 | ```python
563 | APPROX_NAME = "tree_adder"
564 | APPROX_RTL = f"{APPROX_NAME}.v"
565 |
566 | clf.to_verilog_file(APPROX_NAME, APPROX_RTL)
567 | ```
568 |
569 | 5. Evaluate the approximate circuit:
570 |
571 | ```python
572 | approx_circuit = Circuit(APPROX_RTL, "NanGate15nm")
573 | APPROX_OUTPUT = f"{APPROX_NAME}/output.txt"
574 |
575 | APPROX_TB = f"{APPROX_NAME}_tb.v"
576 |
577 | approx_circuit.write_tb(APPROX_TB, INPUT, DATASET_SIZE)
578 |
579 | error = approx_circuit.simulate_and_compute_error(APPROX_TB, ORIGINAL_OUTPUT, APPROX_OUTPUT, "mred")
580 |
581 | print(f"Mean Relative Error: {error * 100}%")
582 | print(f"Original Area: {original_circuit.get_area()}")
583 | print(f"Approximate Area: {approx_circuit.get_area()}")
584 | ```
585 |
586 | This could return the following sample output:
587 |
588 | ```
589 | Mean Relative Error: 22.90%
590 | Original Area: 6.586368
591 | Approximate Area: 3.293184
592 | ```
593 |
594 | In this scenario, we have reduced the circuit's area in half, while only
595 | introducing around ~23% error.
596 |
597 | # Files and Folders
598 |
599 | Files and Folders description:
600 |
601 | | Name | Description | Used |
602 | | ------------------- | ------------------------------------------------------------ | ------ |
603 | | prunning_algorithms | Folder containing pruning techniques implementations. | |
604 | | `inouts.py` | Contains the implementation of `GetInputs` and `GetOutputs` example pruning methods. | |
605 | | `probprun.py` | Contains the implementation of a pseudo Probabilistic Pruning method. `GetOneNode` is a python generator. It will retrieve one node to delete each time it is called. | |
606 | | templates | Folder containing some libraries and scripts used for synthesis. | |
607 | | `NanGate15nm.lib` | | |
608 | | `NanGate15nm.v` | | |
609 | | `synth.ys` | Script to synthesize a circuit using yosys. | |
610 | | `__main__.py` | It executes the tool using the arguments from the command line. **Still in progress**. | **No** |
611 | | `barcas.py` | Is the Pruning Implementation using the InOuts techniques. | **NO** |
612 | | `circuit.py` | Object that represents a circuit as a XML tree. Receives a rtl and a library in order to build the circuit and be able to simulate it. | |
613 | | `circuiterror.py` | Compares two outputs and computes different error metrics. | |
614 | | `demo.py` | This file is a complete example of how the library should be used. | |
615 | | `netlist.py` | This class parses, extracts and represents the circuit from rtl into an object understandable by python. | |
616 | | `poisonoak.config` | This is going to be used along with `__main__.py` in order to execute poisonoak as an app, and not as a library. | **No** |
617 | | `poisonoak.help` | Contains the menu and tool description of the poison oak app. | **No** |
618 | | `synthesis.py` | Executes the synthesis script (in our case yosys) and clean the intermediate files generated. At the end returns the path of the netlist. | |
619 | | `technology.py` | This class parses, extracts and represents the technology library file into an object understandable by python. | |
620 | | `test.py` | This class implements some unit tests for the poison oak library. **Not implemented yet**. | **No** |
621 | | `utils.py` | Some functions not related with any other class but useful. | |
622 |
623 |
--------------------------------------------------------------------------------
/__main__.py:
--------------------------------------------------------------------------------
1 |
2 | from os import path
3 | import sys
4 |
5 |
6 | CONFIG_FILE = "poisonoak/poisonoak.config"
7 | HELP_FILE = "poisonoak/poisonoak.help"
8 |
9 |
10 | def trim(text):
11 | '''
12 | Remove the spaces between text
13 |
14 | Parameters
15 | ----------
16 | text : string
17 | text with white spaces
18 |
19 | Returns
20 | -------
21 | string
22 | text without white spaces
23 | '''
24 | return text.replace(" ", "").replace("\n","")
25 |
26 |
27 | def read_config():
28 | '''
29 | Read the config file to check everything is there
30 |
31 | Returns
32 | -------
33 | boolean
34 | True if all the variables are defined and correct
35 | '''
36 | if (path.exists(CONFIG_FILE)):
37 | with open(CONFIG_FILE) as config:
38 | for line in config.readlines():
39 | if len(line) > 10:
40 | var, value = trim(line).split("=")
41 | if var == "RTL" and path.exists(value):
42 | print (value, "CHECK")
43 | else:
44 | print(f"Option {var} is incorrect")
45 | return False
46 | return True
47 | else:
48 | print("Config File does not exists!")
49 |
50 |
51 | for arg in sys.argv:
52 | if (arg == "-h" or arg == "--help"):
53 | with open(HELP_FILE) as help:
54 | print(help.read())
55 | else:
56 | read_config()
57 |
--------------------------------------------------------------------------------
/barcas.py:
--------------------------------------------------------------------------------
1 | from copy import deepcopy
2 | from circuit import Circuit
3 | from pruning_algorithms.inouts import GetInputs, GetOutputs
4 |
5 | BASE = "circuits/ripple.carry.4b/"
6 | TOP = "RCA_3_0"
7 | MET = "wce"
8 |
9 | RTL = f"{BASE}{TOP}.v"
10 | TB = f"{BASE}{TOP}_tb.v"
11 | SAIF = f"{BASE}{TOP}.saif"
12 | ORIG = f"{BASE}output0.txt"
13 | APPR = f"{BASE}output.txt"
14 |
15 | def log (msg):
16 | with open(f"{BASE}log.txt", "a+") as f:
17 | f.write(msg)
18 | print(msg)
19 |
20 | def barcas(circuit, max_error):
21 |
22 | log(f"Pruning circuit for Max Error of: {max_error}\n")
23 |
24 | actual_error = 0
25 |
26 | last_stable_circuit = deepcopy(circuit)
27 | modified_circuit = deepcopy(circuit)
28 |
29 | for bit in range (0, 4):
30 |
31 | for type in ["i","o"]:
32 |
33 | if type == "i":
34 | inputs = [f"in1[{bit}]",f"in2[{bit}]"]
35 | nodes = GetInputs(modified_circuit.netl_root, inputs)
36 | else:
37 | outputs = [f"out[{bit}]"]
38 | nodes = GetOutputs(modified_circuit.netl_root, outputs)
39 |
40 | #print(nodes)
41 |
42 | for node in nodes:
43 | modified_circuit.delete(node.attrib["var"])
44 |
45 | obtained_error = modified_circuit.simulate(TB, MET, ORIG, APPR)
46 |
47 | nvar = node.attrib["var"];
48 |
49 | msg = f"Node Deleted: {nvar}, error({MET}): {obtained_error}\n"
50 |
51 | log(msg);
52 |
53 | if obtained_error <= max_error:
54 | last_stable_circuit.delete(node.attrib["var"])
55 | actual_error = obtained_error
56 | else:
57 | modified_circuit.undodelete(node.attrib["var"])
58 |
59 |
60 | if (actual_error == max_error):
61 | break
62 |
63 | final_error = last_stable_circuit.simulate(TB, MET, ORIG, APPR, clean=False)
64 |
65 | last_stable_circuit.show(show_deletes=True)
66 | input("Press enter...")
67 |
68 | msg = f"[FINAL] Expected: {max_error}, Obtained: {final_error}\n"
69 | log(msg)
70 |
71 | our_circuit = Circuit(RTL, "NanGate15nm")
72 |
73 | for error in [8]: #range (10, 101, 10):
74 |
75 | our_circuit.exact_output(TB)
76 | barcas(our_circuit, error)
77 | '''
78 | x = threading.Thread(target=barcas, args=(our_circuit, error,))
79 | x.start()
80 | '''
81 |
--------------------------------------------------------------------------------
/circuit.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | from graphviz import Digraph
5 | from os import path, remove, system, rename
6 | from random import randint
7 | from re import findall
8 | import xml.etree.ElementTree as ET
9 |
10 | from circuiterror import compute_error
11 | from netlist import Netlist
12 | from synthesis import synthesis, resynthesis, ys_get_area
13 | from technology import Technology
14 | from utils import get_name, get_random
15 | import numpy as np
16 |
17 |
18 |
19 |
20 |
21 | class Circuit:
22 | '''
23 | Representation of a synthesized rtl circuit
24 |
25 | Attributes
26 | -----------
27 | rtl_file : str
28 | path to the circuit rtl file
29 | tech_file : str
30 | name of the technology library used to map the circuit
31 | topmodule : str
32 | name of the circuit module that we want to synthesize
33 | netl_file : str
34 | path to the synthesized netlist file
35 | tech_root : ElementTree.Element
36 | references to the root element of the Technology Library Cells tree
37 | '''
38 |
39 |
40 | def __init__(self, rtl, tech, saif = ""):
41 | '''
42 | Parse a rtl circuit into a xml tree using a specific technology library
43 |
44 | Parameters
45 | ----------
46 | rtl : string
47 | path to the rtl file
48 | tech : string
49 | path to the technology file
50 | saif : string
51 | path to the saif file
52 | '''
53 |
54 |
55 | self.rtl_file = rtl
56 | self.tech_file = tech
57 | self.topmodule = rtl.split('/')[-1].replace(".v","")
58 | self.netl_file = synthesis (rtl, tech, self.topmodule)
59 | self.technology = Technology(tech)
60 | # extract the usefull attributes of netlist
61 | netlist = Netlist(self.netl_file, self.technology)
62 | self.netl_root = netlist.root
63 | self.inputs = netlist.circuit_inputs
64 | self.outputs = netlist.circuit_outputs
65 |
66 | self.raw_inputs = netlist.raw_inputs
67 | self.raw_outputs = netlist.raw_outputs
68 | self.raw_parameters = netlist.raw_parameters
69 |
70 | if (saif != ""):
71 | self.saif_parser(saif)
72 |
73 | self.output_folder = path.dirname(path.abspath(rtl))
74 |
75 | def get_circuit_xml(self):
76 | '''
77 | Returns the circuit netlist in xml format
78 |
79 | Returns
80 | -------
81 | string
82 | xml string of the circuit tree
83 | '''
84 | return ET.tostring(self.netl_root)
85 |
86 |
87 | def get_node(self, node_var):
88 | '''
89 | Returns the ElementTree object corresponding to the node variable
90 |
91 | Parameters
92 | ----------
93 | node_var : string
94 | name of the node to be searched
95 |
96 | Returns
97 | -------
98 | ElementTree.Element
99 | Node instance we are looking for
100 | '''
101 | return self.netl_root.find(f"./node[@var='{node_var}']")
102 |
103 |
104 | def delete(self, node_var):
105 | '''
106 | Marks a node to be deleted
107 |
108 | Parameters
109 | ----------
110 | node_var : string
111 | name of the node to be deleted
112 | '''
113 | node_to_delete = self.netl_root.find(f"./node[@var='{node_var}']")
114 | if (node_to_delete is not None):
115 | node_to_delete.set("delete", "yes")
116 | else:
117 | print(f"Node {node_var} not found")
118 |
119 | def undodelete(self, node_var):
120 | '''
121 | Removes the delete label from a node
122 |
123 | Parameters
124 | ----------
125 | node_var : string
126 | name of the node to be preserved
127 | '''
128 | node_to_delete = self.netl_root.find(f"./node[@var='{node_var}']")
129 | if (node_to_delete is not None):
130 | node_to_delete.attrib.pop("delete")
131 | else:
132 | print(f"Node {node_var} not found")
133 |
134 | # this are some auxiliary functions for write_to_disk
135 |
136 | def is_node_deletable(self, node):
137 | '''
138 | Returns true if a node can be deleted, returns false if the node should
139 | be assigned a constant instead.
140 |
141 | A node can be deleted if all its children nodes will be deleted as
142 | well. If a node has children nodes or connects directly to an output of
143 | the circuit, then the funcction will return false and the node should
144 | be replaced with a constant.
145 |
146 | Parameters
147 | ----------
148 | node : ElementTree.Element
149 | node we want to examine
150 |
151 | Returns
152 | -------
153 | boolean
154 | true if the node can be deleted
155 | '''
156 |
157 | root = self.netl_root
158 |
159 | node_output = node.findall("output")[0]
160 | wire = node_output.attrib["wire"]
161 |
162 | # children of node
163 | re = f"./node/input[@wire='{wire}']/.."
164 | node_children = root.findall(re)
165 |
166 | # children of node that had to be deleted
167 | re = f"./node[@delete='yes']/input[@wire='{wire}']/.."
168 | node_children_to_be_deleted = root.findall(re)
169 |
170 | # If no nodes have this node as an input, it means that this node must
171 | # connect directly to a circuit output.
172 | connects_to_output = len(node_children) == 0
173 |
174 | some_children_not_deleted = len(node_children_to_be_deleted) < len(node_children)
175 |
176 | node_has_outputs = connects_to_output or some_children_not_deleted
177 | node_can_be_deleted = not node_has_outputs
178 |
179 | return node_can_be_deleted
180 |
181 |
182 | def node_to_constant(self, node):
183 | '''
184 | Returns the constant value for which the node can be replaced
185 |
186 | Parameters
187 | ----------
188 | node : ElementTree.Element
189 | node we want to make constant
190 |
191 | Returns
192 | -------
193 | integer
194 | logic 1 or 0
195 | '''
196 | node_output = node.findall("output")[0]
197 | if "t0" in node_output.attrib:
198 | t1 = int(node_output.attrib["t1"])
199 | t0 = int(node_output.attrib["t0"])
200 | return 1 if t1 > t0 else 0
201 | else:
202 | return 0
203 |
204 |
205 | def get_circuit_wires(self):
206 | '''
207 | Returns an ordered list of every circuit wire
208 |
209 | Returns
210 | -------
211 | array
212 | list of circuit wires names
213 | '''
214 | outputs = self.netl_root.findall("./node/output")
215 | wires = list(set([output.attrib["wire"] for output in outputs]))
216 |
217 | # make sure there are not any inputsoutputs in wires
218 | wires = [wire for wire in wires if wire not in self.inputs]
219 | wires = [wire for wire in wires if wire not in self.outputs]
220 | return wires
221 |
222 |
223 | def get_circuit_nodes(self):
224 | '''
225 | Returns an ordered list of every circuit node
226 |
227 | Returns
228 | -------
229 | array
230 | list of circuit nodes names
231 | '''
232 | nodes = self.netl_root.findall("./node")
233 | return [node.attrib["var"] for node in nodes]
234 |
235 | def get_nodes_to_delete(self):
236 | '''
237 | Returns a list of the nodes variables that will be deleted
238 |
239 | Returns
240 | -------
241 | array
242 | list of nodes variable names
243 | '''
244 | nodes_to_delete = self.netl_root.findall("./node[@delete='yes']")
245 | return [node.attrib["var"] for node in nodes_to_delete]
246 |
247 |
248 | def get_wires_to_be_deleted(self):
249 | '''
250 | Returns two lists with the wires that will be deleted completely and
251 | another list with the wires that should be assigned a constant.
252 |
253 | Returns
254 | -------
255 | ( array, dictionary )
256 | list of wires to delete and a dictionary of wires to be grounded
257 | with their logical value
258 | '''
259 | wires_to_be_deleted = [] # {wire1, wire2, ... }
260 | wires_to_be_assigned = {} # { wire: value, ... }
261 | nodes_to_delete = self.netl_root.findall("./node[@delete='yes']")
262 | for node in nodes_to_delete:
263 | node_output = node.findall("output")[0]
264 | if self.is_node_deletable(node):
265 | # the wire could be DELETED
266 | wire = node_output.attrib["wire"]
267 | wires_to_be_deleted.append(wire)
268 | else:
269 | # the wire needs to be ASSIGNED
270 | wire = node_output.attrib["wire"]
271 | constant = self.node_to_constant(node)
272 | wires_to_be_assigned[wire] = constant
273 | return wires_to_be_deleted, wires_to_be_assigned
274 |
275 |
276 |
277 | def write_to_disk (self, filename=""):
278 | '''
279 | Write the xml circuit into a netlist file considering the nodes to be
280 | deleted (marked with an attribute delete='yes')
281 |
282 | Returns
283 | -------
284 | string
285 | path of the recently created netlist
286 | '''
287 |
288 | def format_io(node, io):
289 | ioputs = node.findall(f"{io}put")
290 | return [f".{x.attrib['name']}({x.attrib['wire']})" for x in ioputs]
291 |
292 | nodes_to_delete = self.get_nodes_to_delete()
293 | to_be_deleted, to_be_assigned = self.get_wires_to_be_deleted()
294 |
295 | filename = filename if filename != "" else str(randint(9999,999999))
296 | filepath = f"{self.output_folder}{path.sep}{filename}.v"
297 |
298 | with open(filepath, 'w') as netlist_file:
299 |
300 | def writeln(file, text):
301 | file.write(text + "\n")
302 |
303 | header = "/* Generated by poisonoak */"
304 | writeln(netlist_file, header)
305 |
306 | params = self.raw_parameters
307 | module = f"module {self.topmodule} ({params});"
308 | writeln(netlist_file, module)
309 |
310 | for wire in self.get_circuit_wires():
311 | if wire not in to_be_deleted:
312 | writeln(netlist_file, f"\twire {wire};")
313 | used_outputs=[]
314 | for output in self.raw_outputs:
315 | if output not in used_outputs:
316 | writeln(netlist_file, "\t" + output)
317 | used_outputs.append(output)
318 | for output in self.raw_inputs:
319 | if output not in used_outputs:
320 | writeln(netlist_file, "\t" + output)
321 | used_outputs.append(output)
322 |
323 | for node_var in self.get_circuit_nodes():
324 | if node_var not in nodes_to_delete:
325 | node = self.get_node(node_var)
326 | instance = f"\t{node.attrib['name']} {node.attrib['var']}"
327 | inputs = format_io(node, "in")
328 | outputs = format_io(node, "out")
329 | instance += f" ({','.join(outputs)},{','.join(inputs)});"
330 | writeln(netlist_file, instance)
331 |
332 | for wire,value in to_be_assigned.items():
333 | assign = f"\tassign {wire} = 1'b{value};"
334 | writeln(netlist_file, assign)
335 |
336 | assignments=self.netl_root.findall('./assignments/assign')
337 | for a in assignments: #support for special assignments
338 | assign=f"\tassign {a.attrib['var']} = {a.attrib['val']};"
339 | writeln(netlist_file, assign)
340 |
341 | writeln(netlist_file, "endmodule")
342 | return filepath
343 |
344 |
345 | def show (self, filename=None, show_deletes=False, view=True, format="png"):
346 | '''
347 | Renders the circuit as an image of the graph.
348 | Requires the graphviz python package to be installed.
349 |
350 | Parameters
351 | ----------
352 | filename: str (defaults to the circuit's name)
353 | Name of the png image to render, it shouldn't include the
354 | extension.
355 | For example filename="circuit" results in a file "circuit.png"
356 | show_deletes : boolean (defaults to False)
357 | nodes to be deleted will be colored in red
358 | view: boolean (defaults to True)
359 | if True, opens the image automatically
360 | format: str (defaults to "png")
361 | The format to create render the image with
362 | '''
363 | root = self.netl_root
364 | f = Digraph(self.topmodule)
365 |
366 | # we get the circuit inputs and outputs
367 | inputs = [i.replace('[','_').replace(']','') for i in self.inputs]
368 | outputs = [o.replace('[','_').replace(']','') for o in self.outputs]
369 |
370 | # inputs will have diamond shape with color
371 | f.attr('node',style='filled',fillcolor='#5bc0eb',shape='diamond')
372 | for i in inputs:
373 | f.node(i)
374 | # outputs will have doublecircle shape with color
375 | f.attr('node',style='filled',fillcolor='#9bc53d',shape='doublecircle')
376 | for o in outputs:
377 | f.node(o)
378 | # nodes to be deleted will be in a different color
379 | if (show_deletes):
380 | f.attr('node',style='filled',fillcolor='#f17e7e',shape='circle')
381 | for p in root.findall("./node[@delete='yes']"):
382 | f.node(p.attrib['var'])
383 | # the rest of the nodes will be a white circle
384 | f.attr('node', style='filled', fillcolor='white', shape='circle')
385 |
386 | # draw the edges
387 | for n in root.findall("node"):
388 | # iterate every node output to find edges to their children
389 | for o in n.findall("output"):
390 | wire = o.attrib["wire"]
391 | children = root.findall(f".//input[@wire='{wire}']/..")
392 | # create a new conection between every node and its child
393 | for c in children:
394 | start = n.attrib["var"]
395 | end = c.attrib["var"]
396 | label = wire
397 | f.edge(start, end, label=label)
398 |
399 | # if node has no children, is connected to a circuit output.
400 | if (len(children) == 0):
401 | start = n.attrib["var"]
402 | end = wire.replace('[','_').replace(']','')
403 | f.edge(start, end)
404 |
405 | # inputs are not represented as nodes, so they have to be connected
406 | for i in n.findall("input"):
407 | wire = i.attrib["wire"]
408 | parents = root.findall(f".//output[@wire='{wire}']/..")
409 |
410 | # if node has no parents, is connected to a circuit input
411 | if (len(parents) == 0):
412 | start = wire.replace('[','_').replace(']','')
413 | end = n.attrib["var"]
414 | f.edge(start, end)
415 |
416 | return(f.render(filename=filename, format=format, view=view, cleanup=True))
417 |
418 |
419 |
420 | def saif_parser (self, saif):
421 | '''
422 | Captures the t0, t1 and tc parameters for each component/variable and store
423 | them directly in the xml file of the netlist
424 |
425 | Parameters
426 | ----------
427 | saif : string
428 | file saif formatted file name
429 | '''
430 | with open(saif, 'r') as technology_file:
431 | content = technology_file.read()
432 |
433 | expreg = r'\((.+)\n.*\(T0 ([0-9]+)\).*\(T1 ([0-9]+)\).*\n.*\(TC ([0-9]+)\).*\n.*\)'
434 | saif_cells = findall(expreg, content)
435 |
436 | for saif_cell in saif_cells:
437 |
438 | t0 = int(saif_cell[1])
439 | t1 = int(saif_cell[2])
440 | total = int(t1 + t0)
441 |
442 | saif_cell_name = saif_cell[0]
443 | saif_cell_t0 = str( int((t0/total)*100) )
444 | saif_cell_t1 = str( int((t1/total)*100) )
445 | saif_cell_tc = saif_cell[3]
446 |
447 | if (saif_cell_name[0] == "w"):
448 | my_saif_cell_name = "_" + saif_cell_name[1:] + "_"
449 | cells = self.netl_root.find(f"./node/output[@wire='{my_saif_cell_name}']")
450 |
451 | if (cells is not None):
452 | cells.set('t0',saif_cell_t0)
453 | cells.set('t1',saif_cell_t1)
454 | cells.set('tc',saif_cell_tc)
455 |
456 | elif ((saif_cell_name[0],saif_cell_name[-1]) == ("_","_")): #Yosys 19.
457 | my_saif_cell_name = "_" + saif_cell_name[1:-1] + "_"
458 | cells = self.netl_root.find(f"./node/output[@wire='{my_saif_cell_name}']")
459 |
460 | if (cells is not None):
461 | cells.set('t0',saif_cell_t0)
462 | cells.set('t1',saif_cell_t1)
463 | cells.set('tc',saif_cell_tc)
464 |
465 | elif (saif_cell_name.replace('\\','') in self.outputs):
466 | my_saif_cell_name = saif_cell_name.replace('\\','')
467 | cells = self.netl_root.find(f"./node/output[@wire='{my_saif_cell_name}']")
468 |
469 | if (cells is not None):
470 | cells.set('t0',saif_cell_t0)
471 | cells.set('t1',saif_cell_t1)
472 | cells.set('tc',saif_cell_tc)
473 |
474 | return saif
475 |
476 | def exact_output (self, testbench, output_file):
477 | '''
478 | Simulates the actual circuit tree (with deletions)
479 | Creates an executable using icarus, end then execute it to obtain the
480 | output of the testbench
481 |
482 | Parameters
483 | ----------
484 | testbench : string
485 | path to the testbench file
486 | metric : string
487 | equation to compute the error
488 | options med, wce, wcre,mred, msed
489 | output_file : string
490 | Path to the output file where simulation results will be written.
491 | The user must provide the full file path and name. If the file
492 | exists, it will be overwritten.
493 | '''
494 |
495 |
496 | name = get_name(5)
497 | rtl = self.write_to_disk(name)
498 |
499 | top = self.topmodule
500 | current_dir=os.path.dirname(__file__)
501 | tech = f"{current_dir}/templates/" + self.tech_file
502 | out = self.output_folder
503 |
504 | """Better to temporarily change cwd when executing iverilog"""
505 | cwd=os.getcwd()
506 | os.chdir(current_dir)
507 |
508 | # - - - - - - - - - - - - - - - Execute icarus - - - - - - - - - - - - -
509 | # iverilog -l tech.v -o executable testbench.v netlist.v
510 | kon = f"iverilog -l \"{tech}.v\" -o \"{out}/{top}\" {testbench} \"{rtl}\""
511 | system(kon)
512 |
513 | # - - - - - - - - - - - - - Execute the testbench - - - - - - - - - - -
514 | system(f"cd \"{out}\"; ./{top}")
515 |
516 | os.chdir(cwd)
517 |
518 | remove(rtl)
519 | remove(f"{out}/{top}")
520 |
521 | rename(out + "/output.txt", output_file)
522 |
523 | return
524 |
525 | def simulate_and_compute_error (self, testbench, metric, exact_output, new_output):
526 | '''
527 | Simulates the actual circuit tree (with deletions)
528 | Creates an executable using icarus, end then execute it to obtain the
529 | output of the testbench
530 |
531 | Parameters
532 | ----------
533 | testbench : string
534 | path to the testbench file
535 | metric : string
536 | equation to compute the error
537 | options med, wce, wcre,mred, msed
538 | exact_output : string
539 | Path to the output file of the original exact circuit to compare
540 | against. This file can be created with the `exact_output` method.
541 | new_output : string
542 | Path to the output file where simulation results will be written.
543 | The user must provide the full file path and name. If the file
544 | exists, it will be overwritten.
545 | clean : bool
546 | if true, deletes all the generated files
547 |
548 | Returns
549 | -------
550 | float
551 | error of the current circuit tree
552 | '''
553 |
554 |
555 | name = get_name(5)
556 | rtl = self.write_to_disk(name)
557 |
558 | top = self.topmodule
559 | tech = "./templates/" + self.tech_file
560 | out = self.output_folder
561 |
562 | """Better to temporarily change cwd when executing iverilog"""
563 | cwd=os.getcwd()
564 | current_dir=os.path.dirname(__file__)
565 | os.chdir(current_dir)
566 |
567 | # - - - - - - - - - - - - - - - Execute icarus - - - - - - - - - - - - -
568 | # iverilog -l tech.v -o executable testbench.v netlist.v
569 | kon = f"iverilog -l \"{tech}.v\" -o \"{out}/{top}\" {testbench} \"{rtl}\""
570 | system(kon)
571 |
572 | # - - - - - - - - - - - - - Execute the testbench - - - - - - - - - - -
573 | system(f"cd \"{out}\"; ./{top}")
574 | os.chdir(cwd)
575 |
576 | rename(out + "/output.txt", new_output)
577 |
578 | error = compute_error(metric, exact_output, new_output)
579 |
580 | remove(rtl)
581 | remove(f"{out}/{top}")
582 |
583 | return error
584 |
585 | def generate_dataset(self, filename, samples, distribution='uniform', **kwargs):
586 | '''
587 |
588 | Generates a dataset of randomly distributed data for each input of the circuit.
589 | By Default, data is written in columns of n-bit hexadecimal numbers, being each column an input and n its bitwidth.
590 | Will create a `dataset` file in the same folder where the original
591 | circuit RTL is located.
592 |
593 | Parameters
594 | ----------
595 | filename: string
596 | Path to the output dataset file.
597 | The user must provide the full file path and name. If the file
598 | exists, it will be overwritten.
599 | samples: int
600 | How many rows of data to generate.
601 | distribution: string
602 | The name of the desired random distribution. Could be:
603 | "gaussian" or "normal" for a normal distribution.
604 | "uniform" or "rectangular" for a uniform distribution.
605 | "triangular" for a triangular distribution.
606 | TODO: Add more distributions
607 |
608 | **kwargs: (optional)
609 |
610 | median: int
611 | The center of the distribution (works only for certain distributions)
612 | std: int
613 | Standard deviation of the destribution (only gaussian/normal distribution)
614 | limits: int tuple
615 | Lower and upper limit of the dataset. By default it takes the whole range of numbers: [0,2^n-1]
616 | format: string
617 | A format identifier to convert data into a desired base. Could be:
618 | x for lowercase Hexadecimal (default), use X for uppercase
619 | d for decimal
620 | b for binary
621 | o for octal
622 | '''
623 |
624 |
625 | data=[]
626 |
627 | format=kwargs['format'] if ('format' in kwargs) else 'x'
628 |
629 |
630 | '''Get inputs information'''
631 | inputs_info={}
632 | for i in self.raw_inputs:
633 | name=re.search(r' (\S+);', i)
634 | bits=re.findall(r'[\:[](\d+)', i)
635 |
636 | if bits:
637 | bitwidth=1+int(bits[0])-int(bits[1])
638 | inputs_info[name]=bitwidth
639 | else:
640 | inputs_info[name]=1
641 |
642 | '''Iterate inputs'''
643 |
644 | for bitwidth in inputs_info.values():
645 | rows=get_random(bitwidth,distribution,samples, **kwargs)
646 | format=f'0{bitwidth}b' if format=='b' else format #ensure right number of bits if binary
647 | data.append([f'{i:{format}}' for i in rows])
648 | data=list(zip(*data)) # Transpose data see: https://stackoverflow.com/questions/10169919/python-matrix-transpose-and-zip
649 | np.savetxt(filename,data,fmt='%s')
650 |
651 | return
652 |
653 | def write_tb(self, filename, dataset_file, iterations=None, timescale= '10ns / 1ps', delay=10, format='h', dump_vcd=False):
654 | '''
655 | Writes a basic testbench for the circuit.
656 |
657 | Parameters
658 | ----------
659 | filename: string
660 | Path to the output testbench file.
661 | The user must provide the full file path and name. If the file
662 | exists, it will be overwritten.
663 | dataset_file: string
664 | Path to the dataset file which can be created with `generate_dataset`.
665 | iterations (optional): int
666 | How many iterations to do (how many inputs pass to the circuit, and outputs write to file).
667 | Requires dataset to be generated, by default it takes the number of rows.
668 | timescale: string
669 | A verilog timescale formatted as: timeunit / timeprecision
670 | delay: int
671 | Delay in time units. Applied at inputs initialization and after each iteration.
672 | format: string
673 | A verilog format string that indicates in which base the input dataset is represented.
674 | 'h' for hexadecimal
675 | 'o' for octal
676 | 'd' for decimal
677 | 'b' for binary
678 |
679 | Returns
680 | -------
681 | path to generated file
682 | '''
683 |
684 | '''Check for existing dataset'''
685 | if iterations is None:
686 | if os.path.exists(dataset_file):
687 | file=open(dataset_file, 'r')
688 | iterations=len(file.read().splitlines())
689 | file.close()
690 | else:
691 | raise FileNotFoundError(f"Dataset file '{dataset_file}' not found. Either create it or manually pass an 'iterations' parameter to Circuit.write_tb.")
692 |
693 |
694 | '''Get inputs/outputs information'''
695 | inputs_info={}
696 | for i in self.raw_inputs:
697 | name=re.search(r' (\S+);', i).group(1)
698 | bits=re.findall(r'[\:[](\d+)', i)
699 |
700 | if bits:
701 | bitwidth=1+int(bits[0])-int(bits[1])
702 | inputs_info[name]=bitwidth
703 | else:
704 | inputs_info[name]=1
705 |
706 | outputs_info={}
707 |
708 | for o in self.raw_outputs:
709 | name=re.search(r' (\S+);', o).group(1)
710 | bits=re.findall(r'[\:[](\d+)', o)
711 |
712 | if bits:
713 | bitwidth=1+int(bits[0])-int(bits[1])
714 | outputs_info[name]=bitwidth
715 | else:
716 | outputs_info[name]=1
717 |
718 | '''Write the header and module definition'''
719 | text= f'/* Generated by AxLS */\n' \
720 | f'`timescale {timescale} \n' \
721 | f'\n' \
722 | f'module {self.topmodule}_tb(); \n' \
723 | f'\n'
724 |
725 | '''Define inputs/outpus reg/wires and variables'''
726 |
727 | for name, bitwidth in zip(outputs_info.keys(), outputs_info.values()):
728 | if bitwidth==1:
729 | text= f'{text}wire {name};\n'
730 | else:
731 | text= f'{text}wire [{bitwidth-1}:0] {name};\n'
732 |
733 |
734 | for name, bitwidth in zip(inputs_info.keys(), inputs_info.values()):
735 | if bitwidth==1:
736 | text= f'{text}reg {name};\n'
737 | else:
738 | text= f'{text}reg [{bitwidth-1}:0] {name};\n'
739 |
740 |
741 | text= f'{text}\n' \
742 | f'integer i, file, mem, temp;\n' \
743 | f'\n' \
744 |
745 | '''Instantiate DUT'''
746 | text= f'{text}{self.topmodule} U0('
747 | params=self.raw_parameters.split(', ')
748 | for p in params[0:-1]:
749 | text= f'{text}{p},'
750 | text= f'{text}{params[-1]});\n' \
751 | f'\n' \
752 |
753 | '''Initial statement'''
754 | text= f'{text}initial begin\n $display("-- Beginning Simulation --");\n\n'
755 | if dump_vcd:
756 | text=f'{text} $dumpfile("./{self.topmodule}.vcd");\n' \
757 | f' $dumpvars(0,{self.topmodule}_tb);\n'
758 |
759 | relative_dataset_path = os.path.relpath(dataset_file, start=os.path.dirname(filename))
760 |
761 | text=f'{text} file=$fopen("output.txt","w");\n' \
762 | f' mem=$fopen("{relative_dataset_path}", "r");\n'
763 | for i in inputs_info.keys():
764 | text=f'{text} {i} = 0;\n'
765 | text=f'{text} #{delay}\n' \
766 | f' for (i=0;i<{iterations};i=i+1) begin\n' \
767 | f' temp=$fscanf(mem,"'
768 | for i in range(len(inputs_info)):
769 | text=f'{text}%{format} '
770 | text=f'{text}\\n"'
771 | for i in inputs_info.keys():
772 | text=f'{text},{i}'
773 | text=f'{text});\n' \
774 | f' #{delay}\n' \
775 | f' $fwrite(file, "'
776 | for o in range(len(outputs_info.keys())):
777 | text=f'{text}%d\\n '
778 | text=f'{text}",'
779 | for o in list(outputs_info.keys())[::-1][0:-1]:
780 | text= f'{text}{o},'
781 | text= f'{text}{list(outputs_info.keys())[0]});\n'\
782 | + f' $display("-- Progress: %d/{iterations} --",i+1);\n'\
783 | f' end\n' \
784 | f' $fclose(file);\n' \
785 | f' $fclose(mem);\n' \
786 | f' $finish;\n' \
787 | f'end\n' \
788 | f'endmodule\n'
789 |
790 | with open(os.path.join(filename), 'w') as file:
791 | file.write(text)
792 | file.close()
793 |
794 | return
795 |
796 | def resynth(self):
797 | '''
798 | Calls resynthesis function to reduce circuit structure using logic synthesis optimizations/mapping
799 |
800 | :return: path-like string
801 | path to resynthetized file
802 | '''
803 | name=get_name(5)
804 | self.netl_file =resynthesis(self.write_to_disk(name),self.tech_file,self.topmodule)
805 |
806 | netlist = Netlist(self.netl_file, self.technology)
807 | self.netl_root = netlist.root
808 | self.inputs = netlist.circuit_inputs
809 | self.outputs = netlist.circuit_outputs
810 | self.raw_inputs = netlist.raw_inputs
811 | self.raw_outputs = netlist.raw_outputs
812 | self.raw_parameters = netlist.raw_parameters
813 |
814 | os.remove(f'{self.output_folder}/{name}.v')
815 |
816 | return self.netl_file
817 |
818 | def get_area(self, method = 'yosys'):
819 | '''
820 | Calls yosys script to estimate circuit area
821 | Add here any other method for area estimation implemented in the future
822 |
823 | :return: string
824 | area estimation value
825 | '''
826 |
827 | if method == 'yosys':
828 | name=get_name(5)
829 | area=ys_get_area(self.write_to_disk(name),self.tech_file,self.topmodule)
830 | os.remove(f'{self.output_folder}/{name}.v')
831 |
832 | return area
833 | else:
834 | raise ValueError(f'{method} is not a valid/implemented area estimation method')
835 |
--------------------------------------------------------------------------------
/circuiterror.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | def extract_numbers(filename):
4 | '''
5 | Reads a txt file and returns the list of numbers inside the file
6 |
7 | Parameters
8 | ----------
9 | filename : string
10 | name of the text file to read
11 |
12 | Returns
13 | -------
14 | array
15 | list of numbers of each row of the file
16 | '''
17 | result = []
18 | file = open(filename, 'r')
19 | rows = file.read().split('\n')
20 | for row in rows:
21 | row = row.replace(' ','')
22 | if row.isdigit():
23 | result.append(int(row))
24 | file.close()
25 | return result
26 |
27 |
28 | def compute_error(metric, original, approximate):
29 | '''
30 | Computes the error between two different testbench output files
31 |
32 | Parameters
33 | ----------
34 | metric : string
35 | equation to measure the error
36 | options med, wce, wcre,mred, msed
37 | original : string
38 | path to the original results text file
39 | approximate : string
40 | path to the approximate results text file
41 | '''
42 |
43 | # Read original output content
44 | original_output = extract_numbers(original)
45 |
46 | # Read modified output content
47 | approximate_output = extract_numbers(approximate)
48 |
49 | original_len = len(original_output)
50 | approx_len = len(approximate_output)
51 |
52 | assert original_len == approx_len, f"The output of the original and the approximate simulations doesn't match: {original_len}!={approx_len}. Make sure both outputs are being generated correctly."
53 |
54 | # compute the error distance ED := |a - a'|
55 | error_distance = [abs(original_output[x] - approximate_output[x])
56 | for x in range(0,len(original_output))]
57 |
58 | square_error_distance = [ error_distance[x]**2 for x in range(0,len(error_distance))]
59 |
60 | # compute the relative error distance RED := ED / a
61 | relative_error_distance = [
62 | 0 if original_output[x] == 0 else error_distance[x]/original_output[x]
63 | for x in range(0,len(original_output))]
64 |
65 | # Error Rate:
66 | if (metric == "er"):
67 | return round(sum((error>0 for error in error_distance))/total,3)
68 |
69 | # Mean Hamming Distance see: https://stackoverflow.com/questions/40875282/fastest-way-to-get-hamming-distance-for-integer-array
70 | if (metric == "hd"):
71 | hamming_distance=np.bitwise_xor(original_output,approximate_output)
72 | hamming_distance=[f'{hd:b}'.count('1') for hd in hamming_distance]
73 | return round(np.mean(hamming_distance),3)
74 |
75 | # Mean Error Distance MED := sum { ED(bj,b) * pj }
76 | if (metric == "med"):
77 | mean_error = sum(error_distance) / len(error_distance)
78 | return round(mean_error,3)
79 |
80 | # Worst Case Error
81 | elif (metric == "wce"):
82 | return max(error_distance)
83 |
84 | # Mean Relative Error Distance
85 | elif (metric == "mred"):
86 | mred = sum(relative_error_distance)/len(relative_error_distance)
87 | return round(mred,3)
88 |
89 | # Mean Square Error Distance
90 | elif (metric == "msed"):
91 | msed = sum(square_error_distance)/len(square_error_distance)
92 | return round(msed,3)
93 |
--------------------------------------------------------------------------------
/demo.py:
--------------------------------------------------------------------------------
1 | from circuit import Circuit
2 | from pruning_algorithms.inouts import GetInputs, GetOutputs
3 | from pruning_algorithms.probprun import GetOneNode
4 |
5 | # name of the circuit we want to parse
6 | RTL = "ALS-benchmark-circuits/BK_16b/BK_16b.v"
7 | SAIF = "ALS-benchmark-circuits/BK_16b/NanGate15nm/BK_16b.saif"
8 |
9 | # Testbench and dataset files to generate
10 | TB = "ALS-benchmark-circuits/BK_16b/BK_16b_tb.v"
11 | DATASET = "ALS-benchmark-circuits/BK_16b/dataset"
12 | SAMPLES = 100_000
13 |
14 | # benchmark result files
15 | EXACT_RESULT = "ALS-benchmark-circuits/BK_16b/output_exact.txt"
16 | APPROX_RESULT = "ALS-benchmark-circuits/BK_16b/output_approx.txt"
17 |
18 | # the demo creates images of the circuit change this constants to avoid
19 | # creating or just opening them
20 | CREATE_IMAGE = True # Set to false to avoid creating image
21 | VIEW_IMAGE = True # Set to false to avoid opening the images
22 |
23 | # Circuit creates a representation of the circuit using python objects
24 | our_circuit = Circuit(RTL, "NanGate15nm", SAIF)
25 |
26 | # Generate the dataset and testbench
27 | our_circuit.generate_dataset(DATASET, SAMPLES)
28 | our_circuit.write_tb(TB, DATASET, iterations=SAMPLES)
29 |
30 | our_circuit.exact_output(TB, EXACT_RESULT)
31 |
32 | # if you want to see the circuit xml
33 | print("\nShowing circuit XML\n---------------------")
34 | print(our_circuit.get_circuit_xml())
35 | print("---------------------")
36 | input("Press any key to continue...")
37 |
38 | # if you want to see the circuit as a graph
39 | if CREATE_IMAGE:
40 | print("\nShowing circuit as a graph")
41 |
42 | our_circuit.show("demo_circuit", view=VIEW_IMAGE, format="png")
43 | input("Press any key to continue...")
44 |
45 | # if you need the circuit inputs / outputs
46 | print("Circuit inputs...")
47 | print(our_circuit.inputs)
48 | print("Circuit outputs...")
49 | print(our_circuit.outputs)
50 |
51 | # the root of the xml tree is at:
52 | print("Root of XML tree: ", our_circuit.netl_root)
53 |
54 | # lets delete a node for example the _101_
55 | our_circuit.delete("_101_")
56 |
57 | # lets plot the circuit showing the nodes to be deleted
58 | if CREATE_IMAGE:
59 | print("\nShowing circuit with node _101_ to be deleted")
60 | our_circuit.show(
61 | "demo_circuit_with_deletion", show_deletes=True, view=VIEW_IMAGE, format="png"
62 | )
63 | input("\nPress any key to continue with InOuts method deletion suggestions...")
64 |
65 | # Lets start with our pruning algorithms
66 |
67 | # Extracts the nodes that can be deleted if inputs of bit 0 are constants
68 | inputs = ["X[0]", "Y[0]"]
69 | depricable_nodes = GetInputs(our_circuit.netl_root, inputs)
70 | print("Nodes to delete if input 0 is constant")
71 | print([n.attrib["var"] for n in depricable_nodes])
72 |
73 | # Extracts the nodes that can be deleted if inputs of bit 3 are constants
74 | inputs = ["X[0]", "Y[0]", "X[1]", "Y[1]", "X[2]", "Y[2]", "X[3]", "Y[3]"]
75 | depricable_nodes = GetInputs(our_circuit.netl_root, inputs)
76 | print("Nodes to delete if input 3 is constant")
77 | print([n.attrib["var"] for n in depricable_nodes])
78 |
79 |
80 | # Extracts the nodes that can be deleted if output of bit 0 is constant
81 | outputs = ["S[0]"]
82 | depricable_nodes = GetOutputs(our_circuit.netl_root, outputs)
83 | print("Nodes to delete if output 0 is constant")
84 | print([n.attrib["var"] for n in depricable_nodes])
85 |
86 | # Extracts the nodes that can be deleted if outputs of bit 3 is constant
87 | outputs = ["S[5]"]
88 | depricable_nodes = GetOutputs(our_circuit.netl_root, outputs)
89 | print("Nodes to delete if output 5 is constant")
90 | print([n.attrib["var"] for n in depricable_nodes])
91 |
92 | # Lets try another method: pseudo probprub.
93 | input("\nPress any key to continue with Pseudo Probrun method deletion suggestions...")
94 | # Every time we call the function it returns the recommended node to delete
95 | # because it was all the time in 1 or 0
96 |
97 | pseudo_probprun = GetOneNode(our_circuit.netl_root)
98 |
99 | node, output, time = next(pseudo_probprun)
100 |
101 | print(
102 | f"ProbPrun suggest delete the node {node} because it's {output} {time}% of the time"
103 | )
104 |
105 | # so lets take out the 10 most useless nodes
106 |
107 | for x in range(10):
108 | node, output, time = next(pseudo_probprun)
109 | print(f"{node} is {output} {time}% of the time")
110 |
111 |
112 | input("Press any key to continue with the simulation of the approximate circuit...")
113 | print("Simulating...")
114 | error = our_circuit.simulate_and_compute_error(TB, EXACT_RESULT, APPROX_RESULT, "med")
115 | print("Simulation finished...")
116 | print("Mean Error Distance of approximate circuit with node _101_ deleted:", error)
117 |
--------------------------------------------------------------------------------
/images/decision_tree_method.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ECASLab/AxLS/a5f1f4a39a491f1965d267b74441a9c67c01d136/images/decision_tree_method.png
--------------------------------------------------------------------------------
/ml_algorithms/decision_tree.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 | from typing import List
3 | import numpy as np
4 | from sklearn.tree import DecisionTreeClassifier
5 | from sklearn.tree._tree import Tree
6 |
7 |
8 | class CircuitVariable:
9 | """Represents a circuit variable with a name and bit width."""
10 |
11 | def __init__(self, name: str, bits: int):
12 | self.name = name
13 | self.bits = bits
14 |
15 |
16 | class DecisionTreeCircuit:
17 | """A decision tree classifier that learns circuits.
18 |
19 | This class wraps a sickit DecisionTreeClassifier with utilities to make it
20 | easy to train on circuit data generated by AxLS and synthesize the result
21 | into a verilog circuit.
22 |
23 | Parameters
24 | ----------
25 | circuit_inputs : list of str
26 | A list of the input variable names in Verilog style, e.g., ['cin', 'in1[3]', 'in1[2]', 'in1[1]', 'in1[0]', 'in2[3]', 'in2[2]', 'in2[1]', 'in2[0]'].
27 |
28 | circuit_outputs : list of str
29 | A list of the output variable names in Verilog style, e.g., ['out1[3]', 'out1[2]', 'out1[1]', 'out1[0]'].
30 |
31 | one_tree_per_output : bool, default=False
32 | By default this class trains 1 single multi-output classifier tree. But
33 | if this is set to True, then it will use multiple classifier trees, a
34 | different one for each circuit output.
35 |
36 | **kwargs : dict
37 | Keyword arguments to be passed to the scikit-learn
38 | DecisionTreeClassifier constructor. For a list of available parameters,
39 | please refer to the scikit-learn documentation:
40 | https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html
41 |
42 | Useful parameters include but are not limited to: max_depth,
43 | """
44 |
45 | clf: DecisionTreeClassifier | List[DecisionTreeClassifier]
46 | one_tree_per_output: bool
47 | inputs: List[CircuitVariable]
48 | outputs: List[CircuitVariable]
49 | _trained: bool
50 |
51 | circuit_inputs: List[str]
52 | circuit_outputs: List[str]
53 |
54 | def __init__(
55 | self,
56 | circuit_inputs: List[str],
57 | circuit_outputs: List[str],
58 | one_tree_per_output=False,
59 | **kwargs,
60 | ):
61 | self.circuit_inputs = circuit_inputs
62 | self.circuit_outputs = circuit_outputs
63 | self.inputs = _parse_circuit_variables(circuit_inputs)
64 | self.outputs = _parse_circuit_variables(circuit_outputs)
65 | self._trained = False
66 | self.one_tree_per_output = one_tree_per_output
67 | if one_tree_per_output:
68 | self.clf = [
69 | DecisionTreeClassifier(**kwargs)
70 | for _ in range(len(self.circuit_outputs))
71 | ]
72 | else:
73 | self.clf = DecisionTreeClassifier(**kwargs)
74 |
75 | def train(self, X: List[List[int]], y: List[List[int]]):
76 | """Train the decision tree classifier(s) with the training set (X, y).
77 |
78 | Parameters
79 | ----------
80 | X : array-like of shape (n_samples, n_features)
81 | The training input samples as integers.
82 |
83 | y : array-like of shape (n_samples, n_outputs)
84 | The target values as integers.
85 | """
86 | input_bit_widths = [input.bits for input in self.inputs]
87 | output_bit_widths = [output.bits for output in self.outputs]
88 |
89 | inputs = _to_binary(X, input_bit_widths)
90 | outputs = _to_binary(y, output_bit_widths)
91 |
92 | if isinstance(self.clf, DecisionTreeClassifier):
93 | self.clf.fit(inputs, outputs)
94 | else:
95 | for clf, outputs in zip(self.clf, outputs.transpose()):
96 | clf.fit(inputs, outputs)
97 |
98 | self._trained = True
99 |
100 | def to_verilog_file(self, topmodule: str, output_file: str):
101 | """
102 | Generate a synthesizable Verilog file representing the learned decision tree(s).
103 |
104 | Parameters
105 | ----------
106 | topmodule : str
107 | The name of the top-level Verilog module.
108 | output_file : str
109 | Path to the output file where the Verilog code will be written.
110 | """
111 |
112 | if not self._trained:
113 | raise RuntimeError(
114 | "This model instance is not trained yet. Call 'train' with appropriate arguments before using this method."
115 | )
116 |
117 | raw_parameters = ", ".join(
118 | [variable.name for variable in self.inputs + self.outputs]
119 | )
120 | raw_inputs = [
121 | f"input {variable.name};"
122 | if variable.bits == 1
123 | else f"input [{variable.bits}:0] {variable.name};"
124 | for variable in self.inputs
125 | ]
126 | raw_outputs = [
127 | f"output {variable.name};"
128 | if variable.bits == 1
129 | else f"output [{variable.bits - 1}:0] {variable.name};"
130 | for variable in self.outputs
131 | ]
132 |
133 | with open(output_file, "w") as f:
134 | f.write(f"module {topmodule} ({raw_parameters});\n")
135 |
136 | for input in raw_inputs:
137 | f.write(f" {input}\n")
138 | for output in raw_outputs:
139 | f.write(f" {output}\n")
140 |
141 | for i, output in enumerate(self.circuit_outputs):
142 | if isinstance(self.clf, DecisionTreeClassifier):
143 | equation = _tree_2_equation(
144 | self.clf.tree_, i, 0, self.circuit_inputs
145 | )
146 | else:
147 | equation = _tree_2_equation(
148 | self.clf[i].tree_, 0, 0, self.circuit_inputs
149 | )
150 | f.write(
151 | f" assign {output} = {equation};\n"
152 | ) # Directly assign the Boolean equation to 'f'
153 | f.write("endmodule\n")
154 |
155 |
156 | def _to_binary(x: List[List[int]], bit_widths: List[int]):
157 | """Convert a list of lists of integers to a binary representation.
158 |
159 | This function takes a list input rows `x` and a list of bit widths
160 | `bit_widths`, which provides the bit width of each input variable, and
161 | converts the input data into a binary representation. The resulting array
162 | has a column for each bit position, with each column containing 0s and 1s
163 | representing the binary values for that bit position.
164 |
165 | The bits go from MSB -> LSB.
166 |
167 | TODO: This function should be put in a common module to be used by future ML
168 | methods when new ones are added.
169 |
170 | Parameters
171 | ----------
172 | x : List[List[int]]
173 | A list of lists of integers, where each inner list represents a row of input data.
174 | bit_widths : List[int]
175 | A list of integers, where each value represents the number of bits to use for the
176 | corresponding column in the input data.
177 |
178 | Returns
179 | -------
180 | numpy.ndarray
181 | A 2D numpy array of shape (len(x), sum(bit_widths)), where each row
182 | represents the binary representation of the corresponding row in the
183 | input data `x`, and each column represents a specific bit position.
184 |
185 | Examples
186 | --------
187 | >>> _to_binary([[1, 1], [2, 2], [3, 0xF]], [2, 4])
188 | array([[0, 1, 0, 0, 0, 1],
189 | [1, 0, 0, 0, 1, 0],
190 | [1, 1, 1, 1, 1, 1]], dtype=uint8)
191 | """
192 | columns = np.transpose(np.array(x))
193 | result = []
194 |
195 | for column, width in zip(columns, bit_widths):
196 | if width <= 8:
197 | dtype = np.uint8
198 | if width <= 16:
199 | dtype = np.uint16
200 | if width <= 32:
201 | dtype = np.uint32
202 | if width <= 64:
203 | dtype = np.uint64
204 | else:
205 | dtype = np.object_
206 |
207 | column = np.array(column, dtype=dtype)
208 | shifts = np.arange(width - 1, -1, -1, dtype=dtype)
209 |
210 | column = column[:, None] >> shifts & 1
211 |
212 | result.append(np.array(column, dtype=np.uint8))
213 |
214 | result = np.column_stack(result)
215 | return result
216 |
217 |
218 | def _parse_circuit_variables(variable_list: List[str]):
219 | """Parse a list of circuit variable names and bit widths.
220 |
221 | TODO: This function should be put in a common module to be used by future ML
222 | methods when new ones are added.
223 |
224 | Parameters
225 | ----------
226 | input_list : List[str]
227 | A list of strings representing circuit variables, where each variable can be
228 | either a single-bit variable (e.g., 'cin') or a multi-bit variable (e.g., 'in1[3]').
229 |
230 | Returns
231 | -------
232 | List[CircuitVariable]
233 | A list of `CircuitVariable` objects, where each object represents a
234 | circuit variable with a name and bit width.
235 | """
236 | variables = OrderedDict()
237 | for item in variable_list:
238 | if "[" in item:
239 | name = item.split("[")[0]
240 | if name not in variables:
241 | variables[name] = CircuitVariable(name, 1)
242 | else:
243 | variables[name] = CircuitVariable(name, variables[name].bits + 1)
244 |
245 | else:
246 | variables[item] = CircuitVariable(item, 1)
247 |
248 | return list(variables.values())
249 |
250 |
251 | def _tree_2_equation(
252 | tree: Tree, output: int, node: int, circuit_inputs: list[str], depth=0
253 | ):
254 | """
255 | Recursively convert a decision tree node into a Boolean equation string.
256 |
257 | Parameters
258 | ----------
259 | tree : sklearn.tree._tree.Tree
260 | The underlying decision tree structure.
261 | output : int
262 | Output index to extract from multi-output trees.
263 | node : int
264 | Current node index in the tree.
265 | circuit_inputs : list of str
266 | Flat list of all bit-level input variable names.
267 | depth : int, optional
268 | Current recursion depth (used for internal tracing/debugging).
269 |
270 | Returns
271 | -------
272 | str or None
273 | A Boolean expression string for the subtree rooted at `node`, or None if
274 | the subtree always evaluates to 0.
275 | """
276 | if tree.feature[node] == -2: # Leaf node
277 | result = tree.value[node][output].argmax()
278 | if result == 0:
279 | return None
280 | else:
281 | return "LEAF_NODE_1"
282 |
283 | else: # Internal node
284 | left_result = _tree_2_equation(
285 | tree, output, tree.children_left[node], circuit_inputs, depth + 1
286 | )
287 | right_result = _tree_2_equation(
288 | tree, output, tree.children_right[node], circuit_inputs, depth + 1
289 | )
290 |
291 | feature = tree.feature[node]
292 |
293 | input = circuit_inputs[feature]
294 | negated_input = f"!{input}"
295 |
296 | match (left_result, right_result):
297 | case (None, None):
298 | return None
299 |
300 | case (None, "LEAF_NODE_1"):
301 | return input
302 | case ("LEAF_NODE_1", None):
303 | return negated_input
304 | case ("LEAF_NODE_1", "LEAF_NODE_1"):
305 | return "LEAF_NODE_1"
306 |
307 | case (str(left), "LEAF_NODE_1"):
308 | return f"{input} | ({left})"
309 | case ("LEAF_NODE_1", str(right)):
310 | return f"{negated_input} | ({right})"
311 |
312 | case (str(left), None):
313 | return f"{negated_input} & ({left})"
314 | case (None, str(right)):
315 | return f"{input} & ({right})"
316 | case (str(left), str(right)):
317 | return f"({negated_input} & ({left})) | ({input} & ({right}))"
318 |
--------------------------------------------------------------------------------
/netlist.py:
--------------------------------------------------------------------------------
1 | import re
2 | import xml.etree.cElementTree as ET
3 |
4 |
5 | class NetlistNode:
6 |
7 | def __init__ (self, name, var):
8 | self.name = name
9 | self.var = var
10 | self.inputs = {}
11 | self.outputs = {}
12 |
13 | def addInput (self, input_name, input_value):
14 | self.inputs[input_name] = input_value
15 |
16 | def addOutput (self, output_name, output_value):
17 | self.outputs[output_name] = output_value
18 |
19 |
20 | class Netlist:
21 | '''
22 | Representation of a verilog circuit
23 |
24 | Attributes
25 | ----------
26 | nodes : list
27 |
28 | root : ElementTree.Element
29 | root element of the circuit tree
30 | circuit_inputs : list
31 | list of the circuit inputs
32 | circuit_outputs : list
33 | list of the circuit inputs
34 | '''
35 |
36 |
37 | def __init__(self, netl_file, technology):
38 | '''
39 | Creates a Netlist object parsing the circuit from the netl_file and the
40 | modules of the technology library file
41 |
42 | Parameters
43 | ----------
44 | netl_file : string
45 | path to the netlist file
46 | technology : Technology
47 | list of tecnology libraries modules
48 | '''
49 | self.nodes = []
50 | self.assignments=[] #Special assignments, like constant outputs
51 |
52 | with open(netl_file, 'r') as circuit_file:
53 | content = circuit_file.read()
54 |
55 | self.raw_outputs, self.circuit_outputs = self.get_outputs(content)
56 | self.raw_inputs, self.circuit_inputs = self.get_inputs(content)
57 |
58 | expreg = r'module [a-zA-Z0-9_]*\s*\(([\s\S]+?)\);'
59 | parameters = re.search(expreg,content)
60 | self.raw_parameters = re.sub('\n','',parameters.group(1))
61 |
62 | assigns = parse_assigns(content)
63 | for a in assigns:
64 | self.assignments.append(a)
65 |
66 | # iterate over every instanced module
67 | expreg = r'([a-zA-Z0-9_]+) (.+) \(([\n\s\t.a-zA-Z0-9\(\[\]\),_]*)\);'
68 | instances = re.findall(expreg,content)
69 | for variable in instances:
70 |
71 | # FIRST: we get the instanciated cell name and parameters
72 | cell_name = variable[0]
73 | var = variable[1]
74 | parameters = variable[2].replace("\n","").replace(" ","")
75 |
76 | # SECOND: we get the cell information from the technology library
77 | # children input/output from cells with name='xxx'
78 | cell_inputs = technology.root.findall(
79 | f"./cell[@name='{cell_name}']/input")
80 |
81 | cell_outputs = technology.root.findall( \
82 | f"./cell[@name='{cell_name}']/output")
83 |
84 | # we are going to create our AST node object
85 | node = NetlistNode (cell_name, var)
86 |
87 | # THIRD: now we iterate over the parameters to know which of them
88 | # are inputs or outputs
89 | expreg2 = r'\.([^(,|\n)]+)\(([^(,|\n)]+)\)'
90 | params = re.findall(expreg2,parameters)
91 | for param in params:
92 | param_name = param[0]
93 | param_wire = param[1]
94 |
95 | # FOURTH: we are going to check if param is an input or output
96 | found = False
97 | for i in cell_inputs:
98 | if param_name == i.text:
99 | node.addInput (param_name, param_wire)
100 | found = True
101 | for o in cell_outputs:
102 | if param_name == o.text:
103 | node.addOutput (param_name, param_wire)
104 | found = True
105 | if (not found):
106 | print ("[ERROR] no input or output for param: " + \
107 | param_name + " at cell " + var + ' ' + cell_name)
108 |
109 |
110 | self.nodes.append(node)
111 |
112 | self.root = self.to_xml()
113 |
114 |
115 | def to_xml(self):
116 | '''
117 | Convert the circuit node list into a xml structure tree
118 |
119 | Returns
120 | -------
121 | ElementTree.Element
122 | references to the root element of the circuit nodes tree
123 |
124 | '''
125 | root = ET.Element("root")
126 |
127 |
128 | for n in self.nodes:
129 | node = ET.SubElement(root, "node")
130 | node.set('name',n.name)
131 | node.set('var',n.var)
132 | for key, value in n.inputs.items():
133 | input_element = ET.SubElement(node, "input")
134 | input_element.set('name', key)
135 | input_element.set('wire', value)
136 | for key, value in n.outputs.items():
137 | output_element = ET.SubElement(node, "output")
138 | output_element.set('name', key)
139 | output_element.set('wire', value)
140 |
141 | circuitinputs = ET.SubElement(root, "circuitinputs")
142 | circuitoutputs = ET.SubElement(root, "circuitoutputs")
143 | circuitassignments=ET.SubElement(root,'assignments')
144 |
145 | for o in self.circuit_outputs:
146 | output_element = ET.SubElement(circuitoutputs, "output")
147 | output_element.set('var', o)
148 | for i in self.circuit_inputs:
149 | input_element = ET.SubElement(circuitinputs, "input")
150 | input_element.set('var', i)
151 | for a in self.assignments:
152 | assignment_element=ET.SubElement(circuitassignments,"assign")
153 | assignment_element.set('var',a[0])
154 | assignment_element.set('val',a[1])
155 |
156 | return root
157 |
158 |
159 | def get_inputs(self, netlist_rtl):
160 | '''
161 | Extracts the input variables of the circuit
162 | TODO: support one bit variables
163 |
164 | The `circuit_inputs` will be returned from MSB -> LSB. This is important
165 | to provide the inputs in the correct order to methods that map a circuit
166 | representation to a Verilog format, like the Decision Tree method.
167 |
168 | Parameters
169 | ----------
170 | netlist_rtl : string
171 | content of the netlist file
172 |
173 | Returns
174 | -------
175 | array
176 | list of circuit intputs
177 | '''
178 | raw_inputs = []
179 | circuit_inputs = []
180 | inputs = re.findall(
181 | r'input\s*(\[([0-9]*):([0-9]*)\])*\s*([a-zA-Z0-9]*)',netlist_rtl)
182 | for i in inputs:
183 | if i[0] != '':
184 | left = int(i[1])
185 | right = int(i[2])
186 | if (left > right):
187 | for x in range(left, right-1, -1):
188 | circuit_inputs.append(i[3]+'['+str(x)+']')
189 | else:
190 | for x in range(left,right+1):
191 | circuit_inputs.append(i[3]+'['+str(x)+']')
192 | raw_inputs.append(f"input [{i[1]}:{i[2]}] {i[3]};")
193 | else:
194 | circuit_inputs.append(f"{i[3]}")
195 | raw_inputs.append(f"input {i[3]};")
196 | return raw_inputs, circuit_inputs
197 |
198 |
199 | def get_outputs(self, netlist_rtl):
200 | '''
201 | Extracts the output variables of the circuit
202 | TODO: support one bit variables
203 |
204 | The `circuit_outputs` will be returned from MSB -> LSB. This is
205 | important to provide the outputs in the correct order to methods that
206 | map a circuit representation to a Verilog format, like the Decision Tree
207 | method.
208 |
209 | Parameters
210 | ----------
211 | netlist_rtl : string
212 | content of the netlist file
213 |
214 | Returns
215 | -------
216 | array
217 | list of circuit outputs
218 | '''
219 | raw_outputs = []
220 | circuit_outputs = []
221 | outputs = re.findall(
222 | r'output\s*(\[([0-9]*):([0-9]*)\])*\s*([a-zA-Z0-9]*)',netlist_rtl)
223 | for o in outputs:
224 | if o[0] != '':
225 | left = int(o[1])
226 | right = int(o[2])
227 | if (left > right):
228 | for x in range(left, right-1, -1):
229 | circuit_outputs.append(f"{o[3]}[{str(x)}]")
230 | else:
231 | for x in range(left,right+1):
232 | circuit_outputs.append(f"{o[3]}[{str(x)}]")
233 | raw_outputs.append(f"output [{o[1]}:{o[2]}] {o[3]};")
234 | else:
235 | circuit_outputs.append(f"{o[3]}")
236 | raw_outputs.append(f"output {o[3]};")
237 | return raw_outputs, circuit_outputs
238 |
239 | def expand_range(name):
240 | '''
241 | Expands a Verilog-style bit range expression into a list of individual bits.
242 |
243 | Parameters
244 | ----------
245 | name : string
246 | A string like "a[3:0]" or "b[7]".
247 |
248 | Returns
249 | -------
250 | List[string]
251 | A list of strings like ["a[3]", "a[2]", "a[1]", "a[0]"].
252 |
253 | Examples
254 | -------
255 | >>> expand_range("a[3:1]")
256 | ['a[3]', 'a[2]', 'a[1]']
257 |
258 | >>> expand_range("x[1:3]")
259 | ['x[1]', 'x[2]', 'x[3]']
260 |
261 | >>> expand_range("y[5]")
262 | ['y[5]']
263 |
264 | >>> expand_range("z")
265 | ['z']
266 | '''
267 | m = re.match(r'(\w+)\[(\d+):(\d+)\]', name)
268 | if not m:
269 | return [name]
270 | var, hi, lo = m.groups()
271 | hi, lo = int(hi), int(lo)
272 | step = -1 if hi > lo else 1
273 | return [f"{var}[{i}]" for i in range(hi, lo + step, step)]
274 |
275 | def expand_concat(expr):
276 | '''
277 | Expands a Verilog-style concatenation expression into a flat list of
278 | individual bits.
279 |
280 | Parameters
281 | ----------
282 | expr : string
283 | A Verilog signal, range, or concatenation like "{a[3:0], b[1], c}".
284 |
285 | Returns
286 | -------
287 | List[string]
288 | A list of strings like ["a[3]", "a[2]", "a[1]", "a[0]", "b[1]", "c"].
289 |
290 | Examples
291 | -------
292 | >>> expand_concat("{ a[2:1], b[0] }")
293 | ['a[2]', 'a[1]', 'b[0]']
294 |
295 | >>> expand_concat("{ x[1], y[3:2], z }")
296 | ['x[1]', 'y[3]', 'y[2]', 'z']
297 |
298 | >>> expand_concat("a[3]")
299 | ['a[3]']
300 |
301 | >>> expand_concat("b[1:0]")
302 | ['b[1]', 'b[0]']
303 | '''
304 | expr = expr.strip()
305 | if expr.startswith('{') and expr.endswith('}'):
306 | inner = expr[1:-1]
307 | parts = [p.strip() for p in inner.split(',')]
308 | bits = []
309 | for p in parts:
310 | bits.extend(expand_range(p))
311 | return bits
312 | else:
313 | return expand_range(expr)
314 |
315 | def parse_assigns(content):
316 | '''
317 | Parses Verilog assign statements and expands any bit ranges into individual
318 | assignments.
319 |
320 | The following cases can result in `assign`s:
321 | - Inputs mapped directly to outputs
322 | - Ports mapped to wires by Yosys
323 | - Constant assignments in resynth
324 |
325 | Parameters
326 | ----------
327 | content : string
328 | A string containing one or more Verilog assign statements.
329 |
330 | Returns
331 | -------
332 | List[Tuple[string, string]]
333 | A list of (lhs, rhs) assignment pairs, one for each individual bit.
334 |
335 | Examples
336 | -------
337 | >>> code = """
338 | ... assign a[2] = b[2];
339 | ... assign foo[1:0] = bar[3:2];
340 | ... assign x = 0;
341 | ... assign { out[4:3], out[0:1] } = { in1[3], in2[1:0], in3[2] };
342 | ... """
343 | >>> parse_assigns(code)
344 | [('a[2]', 'b[2]'),
345 | ('foo[1]', 'bar[3]'), ('foo[0]', 'bar[2]'),
346 | ('x', '0'),
347 | ('out[4]', 'in1[3]'), ('out[3]', 'in2[1]'), ('out[0]', 'in2[0]'), ('out[1]', 'in3[2]')
348 | ]
349 |
350 | TODO: This method can't handle range or concatenated assignments to full
351 | variables. For example in the following case it will cause a "Bit width
352 | mismatch" error even if `out` is a 4 bit variable, because it doesn't know
353 | that:
354 |
355 | assign out = { in1[0:1], in2[0:1] }
356 | '''
357 | expreg = r'assign\s+(.*?)\s*=\s*(.*?);'
358 | assigns = re.findall(expreg, content)
359 | result = []
360 | for lhs, rhs in assigns:
361 | lhs_bits = expand_concat(lhs)
362 | rhs_bits = expand_concat(rhs)
363 | if len(lhs_bits) != len(rhs_bits):
364 | raise ValueError(f"Bit width mismatch: LHS {lhs_bits} != RHS {rhs_bits}")
365 | result.extend(zip(lhs_bits, rhs_bits))
366 | return result
367 |
--------------------------------------------------------------------------------
/poisonoak.config:
--------------------------------------------------------------------------------
1 | RTL=/home/sudohumberto/circuits/brent.kung.16b/UBBKA_15_0_15_0.v
2 |
--------------------------------------------------------------------------------
/poisonoak.help:
--------------------------------------------------------------------------------
1 | In order to use the program as a tool, you need to setup a config file.
2 |
3 | 1. Create a file named 'poisonoak.config' inside poisonoak folder:
4 |
5 | 2. Content of the config files
6 |
--------------------------------------------------------------------------------
/pruning_algorithms/ccarving.py:
--------------------------------------------------------------------------------
1 |
2 | def get_parents(n, root):
3 | '''
4 | Check if node's children has ancestry in the cut
5 | '''
6 | return [root.findall('node/output[@wire="'+i.attrib['wire']+'"]/..') for i in n.findall('input')]
7 |
8 | def check_node_delete_status(n):
9 | #Auxiliary function to check if node is marked to be deleted
10 | if 'delete' in n.keys():
11 | #Return true if the node is not marked to be deleted, returns false otherwise
12 | return n.attrib['delete']!='yes'
13 | else:
14 | #By default include nodes that doesn't have the attribute 'delete'.
15 | #that attribute will be present only in nodes analyzed for pruning in previous iterations.
16 | return True
17 |
18 | class Cut:
19 | '''
20 | A branch in the exploration tree of the circuit carving algorithm. Representing a set of nodes in the circuit's graph
21 | that could be pruned.
22 |
23 | Attributes
24 | -----------
25 |
26 | difference: int
27 | Difference value of the cut.
28 |
29 | nodes: list
30 | A list of nodes included in the cut.
31 |
32 | size: int
33 | How many nodes are in the cut.
34 |
35 | circuit_root:
36 | Root of the circuit tree.
37 | '''
38 |
39 | def __init__(self,netlroot):
40 | self.difference=0
41 | self.nodes=[]
42 | self.size=0
43 | self.circuit_root=netlroot
44 |
45 | def addNode(self,node, diff):
46 | '''
47 |
48 | Adds a new node to the cut
49 |
50 | Parameters
51 | ----------
52 | node: ElementTree.element
53 | a node to append in the cut
54 | diff: integer
55 | Difference to add in the cut.
56 |
57 | '''
58 |
59 | self.nodes.append(node)
60 | self.size=self.size+1
61 | self.difference+=diff
62 |
63 | def deleteNode(self,node, diff):
64 | '''
65 |
66 | delete a node in the cut
67 |
68 | Parameters
69 | ----------
70 | node: ElementTree.element
71 | a node to remove from the cut
72 | diff: integer
73 | Difference to substract in the cut.
74 |
75 | '''
76 |
77 | self.nodes.remove(node)
78 | self.size=self.size-1
79 | self.difference-=diff
80 |
81 | def AddedDiff_aux(self,node, diff='significance'):
82 | '''
83 | auxiliary function to get cut difference after a node expansion
84 |
85 | Parameters
86 | ----------
87 | node: ElementTree.element
88 | a node to append in the cut.
89 | diff: string
90 | The name of the criteria used as difference, as it is in the attributes of node node.
91 |
92 | Returns
93 | -------
94 | integer:
95 | A difference value to add
96 | '''
97 |
98 | d=0 #Added Difference value
99 | if (self.nodes==[]): #Check if empty cut
100 | d+=node.attrib[diff]
101 | return d
102 | else:
103 | #Find outgoing edges
104 | outputs=[o for k in [n.findall('output') for n in self.nodes] for o in k]
105 | wires=[o.attrib['wire'] for o in outputs] #wires connected to each edge
106 |
107 |
108 | n_inputs= [n.attrib['wire'] for n in node.findall('input')] #inputs of node
109 |
110 | #Check if node is a children of the cut
111 | children=[c for c in self.circuit_root.findall('node/input[@wire="'+node.findall('output')[0].attrib['wire']+'"]/..')]#children of node node
112 | children=[c for c in children if check_node_delete_status(c)] #Exclude deleted nodes
113 | if not any([(i in wires) for i in n_inputs]):
114 | if children==[]: #if empty, node's output is also a circuit output
115 | d+=float(self.circuit_root.findall(f'circuitoutputs/output[@var="{node.findall("output")[0].attrib["wire"]}"]')[0].attrib[diff])#its valid, and its significance correspond to the output's significance
116 | return d
117 | else:
118 | children=[c for c in children if c not in self.nodes] #filter children in cut
119 |
120 | for c in children:
121 | parents=[i for k in get_parents(c,self.circuit_root) for i in k ]
122 | parents=[p for p in parents if check_node_delete_status(p)] #filter parents
123 | parents.remove(node)
124 | while parents!=[]:
125 | p=parents.pop(0)
126 | if p in self.nodes and p!=node:
127 | children.remove(c)
128 | break
129 | else:
130 | [parents.append(i) for k in get_parents(p,self.circuit_root) for i in k]
131 |
132 | d+=sum([float(c.attrib[diff]) for c in children])
133 | return d
134 |
135 | def checkDiff(self,node, threshold: float, diff='significance'):
136 | '''
137 |
138 | Checks if adding a certain nod satisfies the Difference Threshold criterion
139 |
140 | Parameters
141 | ----------
142 | node: ElementTree.element
143 | a node to append in the cut.
144 | threshold: integer
145 | upper limit for cut difference value.
146 | diff: string
147 | The name of the criteria used as difference, as it is in the attributes of node node.
148 |
149 | Returns
150 | -------
151 | boolean:
152 | Whether the addition meets difference threshold criterion or not
153 | integer:
154 | A difference value to add
155 |
156 | '''
157 |
158 | d=self.AddedDiff_aux(node, diff)
159 | if self.difference+dcut_threshold
239 |
240 | def expandCut(self, node, diff_threshold, diff='significance', added_nodes=[]):
241 | '''
242 |
243 | Tries to add a node, checking the closure criterion and adding other nodes if needed to maintain closure
244 |
245 | Parameters
246 | ----------
247 | node: ElementTree.element
248 | Node to add to the cut.
249 | added_nodes: list
250 | Nodes list of already added nodes by this function
251 |
252 | Returns
253 | -------
254 | bool:
255 | Whether the cut was succesfully expanded with node or not
256 | list:
257 | nodes added to the cut and their added difference value
258 |
259 | '''
260 |
261 | meets_diff,diff_to_add=self.checkDiff(node, threshold=diff_threshold, diff=diff)
262 | if meets_diff:
263 | meets_closure, nodes_to_add=self.checkClosure(node)
264 | self.addNode(node,diff_to_add)
265 | added_nodes.append([node,diff_to_add])
266 | if meets_closure:
267 | return True, added_nodes
268 | else:
269 | for n in nodes_to_add:
270 | result, added_nodes=self.expandCut(n,diff_threshold,diff, added_nodes)
271 | if not result:
272 | if added_nodes!=[]:
273 | [self.deleteNode(k[0],k[1]) for k in added_nodes]
274 | return False, []#added nodes=[]
275 |
276 | return True, added_nodes #Succesfull expansion
277 | else:
278 | if added_nodes!=[]:
279 | [self.deleteNode(k[0],k[1]) for k in added_nodes]
280 | return False, []
281 |
282 |
283 |
284 | def FindCut(netlroot, diff_threshold, diff='significance', harshness_level=0):
285 | '''
286 |
287 | Parameters
288 | ----------
289 | netlroot: ElementrTree.element
290 | Root of the circuit where cuts will be explored
291 | diff_threshold: int
292 | Upper limit for difference metric in the cut
293 | diff: str
294 | Difference metric used as label for the cut
295 | harshness_level: int
296 | Determines how aggressive the exploration will be, in terms of how many times it wil attempt to expand a cut.
297 | A value of 0 means infinite, exploring the cuts as much as possible.
298 |
299 |
300 | Returns
301 | -------
302 | list:
303 | A list of cuts/nodes that could be pruned
304 | '''
305 |
306 | '''Use threshold to limit cut exploration (Nodes with a difference greater than the threshold would never produce valid cuts)'''
307 | cut_list=[]
308 | while True:
309 | all_nodes=netlroot.findall('./node') #get all nodes
310 | all_nodes=[n for n in all_nodes if float(n.attrib[diff])cut_record:
336 | biggest_cut=None
337 | biggest_cut=Cut(netlroot)
338 | [biggest_cut.addNode(n,0) for n in cut.nodes]
339 | biggest_cut.difference=cut.difference
340 | cut_record=biggest_cut.size
341 |
342 | elif cut_record==cut.size:
343 | if cut.difference=harshness_level) and (harshness_level!=0): #Hard exploration
357 | break
358 | if biggest_cut.nodes==[]:
359 | break
360 | else:
361 | cut_list.append(biggest_cut.nodes)
362 | all_nodes=[n for n in all_nodes if n not in biggest_cut.nodes]
363 |
364 | #Clear for a new expansion
365 | cut=None
366 | cut=Cut(netlroot)
367 | biggest_cut=None
368 | biggest_cut=Cut(netlroot)
369 | cut_record=0
370 | return cut_list
371 |
--------------------------------------------------------------------------------
/pruning_algorithms/glpsignificance.py:
--------------------------------------------------------------------------------
1 |
2 | def GetbySignificance(netlroot, output_significances=[]):
3 | '''
4 |
5 | On each call, the function returns one depricable node using significance as criteria.
6 | Significance is a metric that indicates a how important is a node in the structure of the circuit and its
7 | functionality.
8 | Significance is calculated propagating outputs significance to the inputs.
9 |
10 | See: Schlacther et al., "Design and Applications of Approximate Circuits by Gate-Level Pruning",2017.
11 |
12 | Parameters
13 | ----------
14 | netlroot: ElemntTree.Element
15 | root of the circuit tree object
16 |
17 | output_significances: list
18 | list of significances for circuit outputs,
19 | if empty it is assumed that significance of output node is 2^node (LSB has less significance that MSB)
20 |
21 | Returns
22 | -------
23 | array
24 | a list of nodes and its significance in ascendant order
25 |
26 | '''
27 |
28 | '''Label Nodes if necessary'''
29 | _=LabelCircuit(netlroot,output_significances)
30 |
31 | '''Get and sort all significances'''
32 | nodes=[]
33 | for n in netlroot.findall("./node"):
34 | nodes.append([n.attrib["var"],n.attrib["significance"]])
35 | nodes=sorted(nodes,key= lambda z: z[1])
36 |
37 |
38 | return nodes
39 |
40 | def GetSignificance(netlroot, node, overwrite=False):
41 | '''
42 | A recursive function to calculate and set the significance attribute to each node in a circuit's tree
43 |
44 | Parameters
45 | ----------
46 | netlroot: ElementTree.Element
47 | circuit's tree root
48 |
49 | node: ElementTree.Element
50 | current node to get its significance value
51 |
52 | overwrite: boolean
53 | Whether to overwrite or not existing significance value in the nodes
54 |
55 | Returns
56 | -------
57 | significance: integer
58 | current node's significance value
59 | '''
60 |
61 |
62 | circuit_outputs=[o for o in netlroot.findall("./circuitoutputs/output")] #All circuit outputs
63 | co_names=[o.attrib["var"] for o in circuit_outputs] #circuit outputs names
64 | significance=0 #current node's significance
65 |
66 | for o in node.findall("output"): #for each output of the node...
67 | if o.attrib["wire"] in co_names: #if o matches with a circuit output, sum its significance
68 | significance+=int(netlroot.findall("./circuitoutputs/output[@var='"+o.attrib["wire"]+"']")[0].attrib["significance"])
69 | else:
70 | children=netlroot.findall("./node/input[@wire='"+o.attrib["wire"]+"']/..") #get nodes whose inputs are the same wire as the current output
71 | for child in children:
72 | if overwrite:
73 | significance+=int(GetSignificance(netlroot,child))
74 | elif 'significance' not in child.keys():
75 | significance+=int(GetSignificance(netlroot,child))
76 | else:
77 | significance+=child.attrib['significance']
78 |
79 | node.attrib["significance"]=significance #set node's significance
80 | return significance
81 |
82 | def LabelCircuit(netlroot,output_significances=[], overwrite=False):
83 | '''
84 |
85 | Labels a circuit by significance criterion
86 |
87 | Parameters
88 | ----------
89 | netlroot: ElemntTree.Element
90 | root of the circuit tree object
91 |
92 | overwrite: boolean
93 | Whether to overwrite existing labels
94 |
95 | output_significances: list
96 | list of significances for circuit outputs,
97 | if empty it is assumed that significance of output node is 2^node (LSB has less significance that MSB)
98 |
99 | '''
100 |
101 | outputs=[o for o in netlroot.findall("./circuitoutputs/output")] #All circuit outputs
102 | if output_significances==[]: #check if not custom output significances were given
103 | output_significances=[2**i for i in range(len(outputs))]
104 | elif len(output_significances)!=len(outputs): #if custom significances does not match...
105 | raise ValueError("Output significances length does not match with circuit number of outputs")
106 |
107 | for o,s in zip(outputs,output_significances):#set output significances
108 | o.attrib["significance"]=str(s)
109 |
110 | '''Get all the nodes connected to inputs'''
111 | inputs=[i for i in netlroot.findall("./circuitinputs/input")]
112 | parents=[]
113 | for i in inputs:
114 | [parents.append(p) for p in netlroot.findall("./node/input[@wire='"+i.attrib["var"]+"']/..") if p not in parents]
115 |
116 | '''Calculate significance for all the parent nodes (and thus, for all the children nodes due recursivity)'''
117 | for p in parents:
118 | GetSignificance(netlroot,p,overwrite)
119 |
120 | return 0
121 |
122 |
--------------------------------------------------------------------------------
/pruning_algorithms/inouts.py:
--------------------------------------------------------------------------------
1 |
2 | from copy import copy
3 |
4 | def GetInputsAux(netl_root, node, constants, path):
5 | '''
6 | Appends nodes that could be deleted to the path variable
7 | Those nodes can be deleted because their inputs are constant values
8 |
9 | Parameters
10 | ----------
11 | netl_root : ElementTree.Element
12 | root of the circuit tree
13 | node : ElementTree.Element
14 | node that we are processing
15 | constants : array
16 | list of wires that could be replaced by a constant value
17 | path : array
18 | list of ElementTree.Element nodes that can be deleted
19 | '''
20 | node_inputs = node.findall("input")
21 | node_inputs = [i for i in node_inputs if i.attrib["wire"] not in constants]
22 | if (len(node_inputs) == 0):
23 | # all input wires are constants
24 | if node not in path:
25 | path.append(node)
26 | for output in node.findall("output"):
27 | o = output.attrib["wire"]
28 | if (o not in constants):
29 | constants.append(o)
30 | children = netl_root.findall(f"./node/input[@wire='{o}']/..")
31 | for child in children:
32 | GetInputsAux(netl_root, child, constants, path)
33 |
34 |
35 | def GetInputs(netl_root, inputs):
36 | '''
37 | Returns nodes that could be deleted because inputs[bit] are constant values
38 |
39 | Parameters
40 | ----------
41 | netl_root : ElementTree.Element
42 | root of the circuit tree
43 | inputs : array
44 | list of inputs variable names to be pruned
45 |
46 | Returns
47 | -------
48 | array
49 | List of elements that can be deleted
50 | '''
51 | path = []
52 | '''
53 | inputs = netl_root.findall("./circuitinputs/input")
54 | input_wires = [i.attrib["var"] for i in inputs]
55 | constants = []
56 |
57 | # get the input constants
58 | for b in range(0,bit+1):
59 | constant_wires = [w for w in input_wires if f"[{str(b)}]" in w]
60 | constants += constant_wires
61 | '''
62 | constants = copy(inputs) #Avoid circuit structure changes
63 | # start iterating from the nodes that have constant inputs
64 | for c in constants:
65 | children = netl_root.findall(f"./node/input[@wire='{c}']/..")
66 | for c in children:
67 | GetInputsAux(netl_root, c, constants, path)
68 |
69 | return path
70 |
71 |
72 | def GetOutputsAux(netl_root, node, constants, path):
73 | '''
74 | Appends nodes that could be deleted to the path variable
75 | Those nodes can be deleted because their outputs only affect other constant
76 |
77 | Parameters
78 | ----------
79 | netl_root : ElementTree.Element
80 | root of the circuit tree
81 | node : ElementTree.Element
82 | node that we are processing
83 | constants : array
84 | list of wires that could be replaced by a constant value
85 | path : array
86 | list of ElementTree.Element nodes that can be deleted
87 | '''
88 | node_output = node.findall("output")[0].attrib["wire"]
89 |
90 | children = netl_root.findall(
91 | "./node/input[@wire='" + node_output + "']/..")
92 |
93 | children_to_keep = [c for c in children if c.attrib["var"] not in constants]
94 |
95 | if (len(children_to_keep) == 0):
96 | #node.set ("delete", "yes")
97 | if node.attrib["var"] not in constants:
98 | constants.append(node.attrib["var"])
99 | if node not in path:
100 | path.append(node)
101 |
102 | # keep going back
103 | for input in node.findall("input"):
104 | xpath = "./node/output[@wire='" + input.attrib["wire"] + "']/.."
105 | parents = netl_root.findall(xpath)
106 | for parent in parents:
107 | GetOutputsAux (netl_root, parent, constants, path)
108 |
109 |
110 | def GetOutputs(netl_root, outputs):
111 | '''
112 | Returns nodes that could be deleted because outputs[bit] are constant values
113 |
114 | Parameters
115 | ----------
116 | netl_root : ElementTree.Element
117 | root of the circuit tree
118 | outputs : array
119 | list of output variable names to be pruned
120 |
121 | Returns
122 | -------
123 | array
124 | List of elements that can be deleted
125 | '''
126 | path = []
127 |
128 | constants = copy(outputs) #Avoid circuit structure changes
129 | for output in outputs:
130 | nodes = netl_root.findall(f"./node/output[@wire='{output}']/..")
131 | for node in nodes:
132 | GetOutputsAux(netl_root, node, constants, path)
133 |
134 | return path
135 |
--------------------------------------------------------------------------------
/pruning_algorithms/probprun.py:
--------------------------------------------------------------------------------
1 |
2 | def GetOneNode(netl_root):
3 | '''
4 | Each time is called, return a node that could be replaced because its
5 | value is logic 0 or logic 1 almost always
6 |
7 | Nodes are ordered by the time they are on logic 0 or 1 and then returned
8 |
9 | Parameters
10 | ----------
11 | netl_root : ElementTree.Element
12 | root element of the circuit tree
13 |
14 | Returns
15 | -------
16 | array
17 | list of the node to be pruned, logic 1/0 and percentage of time it has
18 | this value wired
19 | '''
20 | nodes_info = []
21 | nodes = netl_root.findall("node")
22 |
23 | for node in nodes:
24 | var = node.attrib["var"]
25 | node_output = node.findall("output")[0]
26 | if "t1" in node_output.attrib:
27 | t1 = int(node_output.attrib["t1"])
28 | t0 = int(node_output.attrib["t0"])
29 | if t1 > t0:
30 | nodes_info.append([var, '1', t1])
31 | else:
32 | nodes_info.append([var, '0', t0])
33 | nodes_info = sorted(nodes_info, key=lambda z: z[2], reverse=True)
34 |
35 | while (len(nodes_info) > 0):
36 | result = nodes_info[0]
37 | nodes_info = nodes_info[1:]
38 | yield result
39 |
--------------------------------------------------------------------------------
/synthesis.py:
--------------------------------------------------------------------------------
1 |
2 | import os
3 | import re
4 |
5 | def synthesis (rtl, tech, topmodule):
6 | '''
7 | Synthetizes a circuit file and map it to a specific techonolgy
8 |
9 | Parameters
10 | ----------
11 | rtl : str
12 | path of the rtl circuit file
13 | tech : str
14 | path of the technology file
15 | topmodule : str
16 | name of the circuit we want to sintetize
17 | tool : str
18 | name of tool we are going to use to sintetize
19 |
20 | Returns
21 | -------
22 | str
23 | path of the sintetized netlist file
24 |
25 | '''
26 |
27 | # - - - - - - - - - - - - - Copy the synth.ys file - - - - - - - - - - - -
28 | current_dir=os.path.dirname(__file__)
29 | file = open(f"{current_dir}/templates/synth.ys","r")
30 | file_text = file.read()
31 | file.close()
32 |
33 | netlist_path = os.path.dirname(rtl) + "/netlist.v"
34 |
35 | file_text = file_text.replace("[[RTLFILENAME]]", f'"{rtl}"')
36 | file_text = file_text.replace("[[TOPMODULE]]", topmodule)
37 | file_text = file_text.replace("[[NETLIST]]", f'"{netlist_path}"')
38 | file_text = file_text.replace("[[LIBRARY]]", f'"{current_dir}/templates/{tech}.lib"')
39 | file_text = file_text.replace("[[LIBRARYABC]]", f'"{current_dir}/templates/{tech}.lib"')
40 |
41 | file = open('synth.ys',"w")
42 | file.write(file_text)
43 | file.close()
44 |
45 | # - - - - - - - - - - - - - - - Execute yosys - - - - - - - - - - - - - -
46 |
47 | os.system ('yosys synth.ys;')
48 |
49 | # - - - - - - - - - - - - - Delete temporal Files - - - - - - - - - - - -
50 |
51 | os.remove ("synth.ys")
52 |
53 | return netlist_path
54 |
55 | def resynthesis(netlist, tech, topmodule):
56 |
57 | '''
58 |
59 | Pass a synthetized circuit to Yosys, reading the tech source file to repeat the synthesis.
60 |
61 | :param netlist: string
62 | Synthetized circuit netlist
63 | :param tech: string
64 | Name of the technology library
65 | :param topmodule: string
66 | Topmodule of the circuit
67 | :return:
68 | path-like string
69 | Path to re-synthetized netlist
70 | '''
71 |
72 |
73 | current_dir=os.path.dirname(__file__)
74 | file = open(f"{current_dir}/templates/resynth.ys","r")
75 | file_text = file.read()
76 | file.close()
77 |
78 | netlist_path = os.path.dirname(netlist) + "/netlist.v"
79 |
80 | file_text = file_text.replace("[[RTLFILENAME]]", netlist)
81 | file_text = file_text.replace("[[TOPMODULE]]", topmodule)
82 | file_text = file_text.replace("[[TECHNOLOGY]]", f'{current_dir}/templates/{tech}.v')
83 | file_text = file_text.replace("[[NETLIST]]", netlist_path)
84 | file_text = file_text.replace("[[LIBRARY]]", f"{current_dir}/templates/{tech}.lib")
85 | file_text = file_text.replace("[[LIBRARYABC]]", f"{current_dir}/templates/{tech}.lib")
86 |
87 | file = open('resynth.ys',"w")
88 | file.write(file_text)
89 | file.close()
90 |
91 | # - - - - - - - - - - - - - - - Execute yosys - - - - - - - - - - - - - -
92 |
93 | os.system ('yosys resynth.ys;')
94 |
95 | # - - - - - - - - - - - - - Delete temporal Files - - - - - - - - - - - -
96 |
97 | os.remove ("resynth.ys")
98 |
99 | return netlist_path
100 |
101 | def ys_get_area(netlist, tech, topmodule):
102 |
103 | '''
104 |
105 | Opens the circuit in Yosys and runs stat command to estimate area. Result is parsed
106 | from a temporary log file generated.
107 |
108 | :param netlist: string
109 | Synthetized circuit netlist
110 | :param tech: string
111 | Name of the technology library
112 | :param topmodule: string
113 | Topmodule of the circuit
114 | :return:
115 | string
116 | Area estimation obtained from yosys stat command
117 | '''
118 |
119 | current_dir=os.path.dirname(__file__)
120 | file = open(f"{current_dir}/templates/stat.ys","r")
121 | file_text = file.read()
122 | file.close()
123 |
124 | yosys_log_path = os.path.dirname(netlist) + "/yosys_log.txt"
125 |
126 | file_text = file_text.replace("[[RTLFILENAME]]", f'"{netlist}"')
127 | file_text = file_text.replace("[[TECHNOLOGY]]", f'"{current_dir}/templates/{tech}.v"')
128 | file_text = file_text.replace("[[TOPMODULE]]", topmodule)
129 | file_text = file_text.replace("[[LIBRARY]]", f'"{current_dir}/templates/{tech}.lib"')
130 |
131 | file = open('stat.ys',"w")
132 | file.write(file_text)
133 | file.close()
134 |
135 | # - - - - - - - - - - - - - - - Execute yosys - - - - - - - - - - - - - -
136 |
137 | os.system (f'yosys stat.ys -l \"{yosys_log_path}\"')
138 |
139 | # - - - - - - - - - - - - - - - Parse Area - - - - - - - - - - - - - - -
140 |
141 | with open(yosys_log_path, "r") as read_file:
142 | text = read_file.read()
143 | pattern = r'Chip area for module\s\'[\s\S]*\':\s([\d\.]*)'
144 | area_pattern = re.findall(pattern, text)
145 | if (area_pattern):
146 | area = area_pattern[0]
147 | else:
148 | area = 0
149 | read_file.close()
150 |
151 | # - - - - - - - - - - - - - Delete temporal Files - - - - - - - - - - - -
152 |
153 | os.remove ("stat.ys")
154 | os.remove (yosys_log_path)
155 |
156 | return area
157 |
--------------------------------------------------------------------------------
/technology.py:
--------------------------------------------------------------------------------
1 | from re import match, findall
2 | import xml.etree.cElementTree as ET
3 |
4 | from pathlib import Path
5 |
6 | class TechLibCell:
7 | '''
8 | Intermediate representation of a technology library cell
9 |
10 | Attributes
11 | -----------
12 | name : str
13 | name of the technology library cell
14 | inputs : array
15 | inputs of the technology library cell
16 | outputs : array
17 | outputs of the technology library cell
18 | '''
19 |
20 | def __init__ (self, name, inputs, outputs):
21 | self.name = name
22 | self.inputs = inputs
23 | self.outputs = outputs
24 |
25 |
26 | class Technology:
27 | '''
28 | Representates the information of the technology file using an xml structure
29 |
30 | Attributes
31 | -----------
32 | cells : array
33 | list of Technology Library Cells
34 | root : ElementTree.Element
35 | object that references the root element of the Technology Library tree
36 | '''
37 |
38 | def __init__(self, tech):
39 | self.cells = []
40 |
41 | with open(f"{Path(__file__).parent}/templates/{tech}.v", 'r') as technology_file:
42 | content = technology_file.read()
43 |
44 | # split the technology file in modules
45 | modules = [f"module{line}module" for line in content.split('module')]
46 |
47 | for module in modules:
48 | if ('input' not in module or 'output' not in module):
49 | continue
50 | elif ('primitive' in module):
51 | continue
52 | else:
53 | # extract the module information
54 | header = match(r'module (.+) \((.+)\);', module)
55 | module_name = header.group(1)
56 | module_inputs = findall(r'input[\s\t]+(.+);', module)
57 | module_outputs = findall(r'output[\s\t]+(.+);', module)
58 | # there was a fix to remove commas here
59 |
60 | self.cells.append(
61 | TechLibCell(module_name,module_inputs,module_outputs)
62 | )
63 |
64 | self.root = self.to_xml()
65 |
66 |
67 | def to_xml(self):
68 | '''
69 | Converts the cells (modules) of the Technology Library into a xml file
70 |
71 | Returns
72 | -------
73 | ElementTree.Element
74 | Reference to the root node of the technology library modules tree
75 |
76 | '''
77 |
78 | root = ET.Element("root")
79 | for c in self.cells:
80 | cell = ET.SubElement(root, "cell")
81 | cell.set('name',c.name)
82 | for i in c.inputs:
83 | ET.SubElement(cell, "input").text = i
84 | for o in c.outputs:
85 | ET.SubElement(cell, "output").text = o
86 |
87 |
88 |
89 | return root
90 |
--------------------------------------------------------------------------------
/templates/NanGate15nm.v:
--------------------------------------------------------------------------------
1 | //
2 | // ******************************************************************************
3 | // * *
4 | // * Copyright (C) 2004-2014, Nangate Inc. *
5 | // * All rights reserved. *
6 | // * *
7 | // * Nangate and the Nangate logo are trademarks of Nangate Inc. *
8 | // * *
9 | // * All trademarks, logos, software marks, and trade names (collectively the *
10 | // * "Marks") in this program are proprietary to Nangate or other respective *
11 | // * owners that have granted Nangate the right and license to use such Marks. *
12 | // * You are not permitted to use the Marks without the prior written consent *
13 | // * of Nangate or such third party that may own the Marks. *
14 | // * *
15 | // * This file has been provided pursuant to a License Agreement containing *
16 | // * restrictions on its use. This file contains valuable trade secrets and *
17 | // * proprietary information of Nangate Inc., and is protected by U.S. and *
18 | // * international laws and/or treaties. *
19 | // * *
20 | // * The copyright notice(s) in this file does not indicate actual or intended *
21 | // * publication of this file. *
22 | // * *
23 | // * NGLibraryCharacterizer, Development_version_64 - build 201405281900 *
24 | // * *
25 | // ******************************************************************************
26 | //
27 | // * Default delays
28 | // * comb. path delay : 0.1
29 | // * seq. path delay : 0.1
30 | // * delay cells : 0.1
31 | // * timing checks : 0.1
32 | //
33 | // * NTC Setup
34 | // * Export NTC sections : true
35 | // * Combine setup / hold : true
36 | // * Combine recovery/removal: true
37 | //
38 | // * Extras
39 | // * Export `celldefine : false
40 | // * Export `timescale : -
41 | //
42 | `timescale 1ns/10ps
43 | module AND2_X1 (A1, A2, Z);
44 | input A1;
45 | input A2;
46 | output Z;
47 |
48 | and(Z, A1, A2);
49 | endmodule
50 |
51 | module AND2_X2 (A1, A2, Z);
52 | input A1;
53 | input A2;
54 | output Z;
55 |
56 | and(Z, A1, A2);
57 | endmodule
58 |
59 | module AND3_X1 (A1, A2, A3, Z);
60 | input A1;
61 | input A2;
62 | input A3;
63 | output Z;
64 |
65 | and(Z, i_12, A3);
66 | and(i_12, A1, A2);
67 | endmodule
68 |
69 | module AND3_X2 (A1, A2, A3, Z);
70 | input A1;
71 | input A2;
72 | input A3;
73 | output Z;
74 |
75 | and(Z, i_18, A3);
76 | and(i_18, A1, A2);
77 | endmodule
78 |
79 | module AND4_X1 (A1, A2, A3, A4, Z);
80 | input A1;
81 | input A2;
82 | input A3;
83 | input A4;
84 | output Z;
85 |
86 | and(Z, i_0, A4);
87 | and(i_0, i_1, A3);
88 | and(i_1, A1, A2);
89 | endmodule
90 |
91 | module AND4_X2 (A1, A2, A3, A4, Z);
92 | input A1;
93 | input A2;
94 | input A3;
95 | input A4;
96 | output Z;
97 |
98 | and(Z, i_0, A4);
99 | and(i_0, i_1, A3);
100 | and(i_1, A1, A2);
101 | endmodule
102 |
103 | module ANTENNA (I);
104 | input I;
105 |
106 | endmodule
107 |
108 | module AOI21_X1 (A1, A2, B, ZN);
109 | input A1;
110 | input A2;
111 | input B;
112 | output ZN;
113 |
114 | not(ZN, i_18);
115 | or(i_18, i_19, B);
116 | and(i_19, A1, A2);
117 | endmodule
118 |
119 | module AOI21_X2 (A1, A2, B, ZN);
120 | input A1;
121 | input A2;
122 | input B;
123 | output ZN;
124 |
125 | not(ZN, i_12);
126 | or(i_12, i_13, B);
127 | and(i_13, A1, A2);
128 | endmodule
129 |
130 | module AOI22_X1 (A1, A2, B1, B2, ZN);
131 | input A1;
132 | input A2;
133 | input B1;
134 | input B2;
135 | output ZN;
136 |
137 | not(ZN, i_0);
138 | or(i_0, i_1, i_2);
139 | and(i_1, A1, A2);
140 | and(i_2, B1, B2);
141 | endmodule
142 |
143 | module AOI22_X2 (A1, A2, B1, B2, ZN);
144 | input A1;
145 | input A2;
146 | input B1;
147 | input B2;
148 | output ZN;
149 |
150 | not(ZN, i_0);
151 | or(i_0, i_1, i_2);
152 | and(i_1, A1, A2);
153 | and(i_2, B1, B2);
154 | endmodule
155 |
156 | module BUF_X1 (I, Z);
157 | input I;
158 | output Z;
159 |
160 | buf(Z, I);
161 | endmodule
162 |
163 | module BUF_X2 (I, Z);
164 | input I;
165 | output Z;
166 |
167 | buf(Z, I);
168 | endmodule
169 |
170 | module BUF_X4 (I, Z);
171 | input I;
172 | output Z;
173 |
174 | buf(Z, I);
175 | endmodule
176 |
177 | module BUF_X8 (I, Z);
178 | input I;
179 | output Z;
180 |
181 | buf(Z, I);
182 | endmodule
183 |
184 | module BUF_X12 (I, Z);
185 | input I;
186 | output Z;
187 |
188 | buf(Z, I);
189 | endmodule
190 |
191 | module BUF_X16 (I, Z);
192 | input I;
193 | output Z;
194 |
195 | buf(Z, I);
196 | endmodule
197 |
198 | module CLKBUF_X1 (I, Z);
199 | input I;
200 | output Z;
201 |
202 | buf(Z, I);
203 | endmodule
204 |
205 | module CLKBUF_X2 (I, Z);
206 | input I;
207 | output Z;
208 |
209 | buf(Z, I);
210 | endmodule
211 |
212 | module CLKBUF_X4 (I, Z);
213 | input I;
214 | output Z;
215 |
216 | buf(Z, I);
217 | endmodule
218 |
219 | module CLKBUF_X8 (I, Z);
220 | input I;
221 | output Z;
222 |
223 | buf(Z, I);
224 | endmodule
225 |
226 | module CLKBUF_X12 (I, Z);
227 | input I;
228 | output Z;
229 |
230 | buf(Z, I);
231 | endmodule
232 |
233 | module CLKBUF_X16 (I, Z);
234 | input I;
235 | output Z;
236 |
237 | buf(Z, I);
238 | endmodule
239 |
240 | `ifdef PRIMITIVES
241 | primitive \seq_CLKGATETST_X1 (QD, CLK, nextstate, NOTIFIER);
242 | output QD;
243 | input CLK;
244 | input nextstate;
245 | input NOTIFIER;
246 | reg QD;
247 |
248 | table
249 | // CLK nextstate NOTIFIER : @QD : QD
250 | 0 0 ? : ? : 0;
251 | 0 1 ? : ? : 1;
252 | 1 ? ? : ? : -; // Ignore non-triggering clock edge
253 | ? * ? : ? : -; // Ignore all edges on nextstate
254 | ? ? * : ? : x; // Any NOTIFIER change
255 | endtable
256 | endprimitive
257 |
258 | module CLKGATETST_X1 (CLK, E, TE, Q);
259 | input CLK;
260 | input E;
261 | input TE;
262 | output Q;
263 | reg NOTIFIER;
264 |
265 | and(Q, CLK, QD);
266 | \seq_CLKGATETST_X1 (QD, CLK, nextstate, NOTIFIER);
267 | not(QDn, QD);
268 | or(nextstate, E, TE);
269 | endmodule
270 |
271 | primitive \seq_DFFRNQ_X1 (IQ, RN, nextstate, CLK, NOTIFIER);
272 | output IQ;
273 | input RN;
274 | input nextstate;
275 | input CLK;
276 | input NOTIFIER;
277 | reg IQ;
278 |
279 | table
280 | // RN nextstate CLK NOTIFIER : @IQ : IQ
281 | ? 0 r ? : ? : 0;
282 | 1 1 r ? : ? : 1;
283 | ? 0 * ? : 0 : 0; // reduce pessimism
284 | 1 1 * ? : 1 : 1; // reduce pessimism
285 | 1 * ? ? : ? : -; // Ignore all edges on nextstate
286 | 1 ? n ? : ? : -; // Ignore non-triggering clock edge
287 | 0 ? ? ? : ? : 0; // RN activated
288 | * ? ? ? : 0 : 0; // Cover all transitions on RN
289 | ? ? ? * : ? : x; // Any NOTIFIER change
290 | endtable
291 | endprimitive
292 |
293 | module DFFRNQ_X1 (D, RN, CLK, Q);
294 | input D;
295 | input RN;
296 | input CLK;
297 | output Q;
298 | reg NOTIFIER;
299 |
300 | \seq_DFFRNQ_X1 (IQ, RN, nextstate, CLK, NOTIFIER);
301 | not(IQN, IQ);
302 | buf(Q, IQ);
303 | buf(nextstate, D);
304 | endmodule
305 |
306 | primitive \seq_DFFSNQ_X1 (IQ, SN, nextstate, CLK, NOTIFIER);
307 | output IQ;
308 | input SN;
309 | input nextstate;
310 | input CLK;
311 | input NOTIFIER;
312 | reg IQ;
313 |
314 | table
315 | // SN nextstate CLK NOTIFIER : @IQ : IQ
316 | 1 0 r ? : ? : 0;
317 | ? 1 r ? : ? : 1;
318 | 1 0 * ? : 0 : 0; // reduce pessimism
319 | ? 1 * ? : 1 : 1; // reduce pessimism
320 | 1 * ? ? : ? : -; // Ignore all edges on nextstate
321 | 1 ? n ? : ? : -; // Ignore non-triggering clock edge
322 | 0 ? ? ? : ? : 1; // SN activated
323 | * ? ? ? : 1 : 1; // Cover all transitions on SN
324 | ? ? ? * : ? : x; // Any NOTIFIER change
325 | endtable
326 | endprimitive
327 |
328 | module DFFSNQ_X1 (D, SN, CLK, Q);
329 | input D;
330 | input SN;
331 | input CLK;
332 | output Q;
333 | reg NOTIFIER;
334 |
335 | \seq_DFFSNQ_X1 (IQ, SN, nextstate, CLK, NOTIFIER);
336 | not(IQN, IQ);
337 | buf(Q, IQ);
338 | buf(nextstate, D);
339 | endmodule
340 | `endif
341 |
342 | module FA_X1 (A, B, CI, CO, S);
343 | input A;
344 | input B;
345 | input CI;
346 | output CO;
347 | output S;
348 |
349 | or(CO, i_12, i_15);
350 | or(i_12, i_13, i_14);
351 | and(i_13, B, CI);
352 | and(i_14, B, A);
353 | and(i_15, CI, A);
354 | not(S, i_20);
355 | or(i_20, i_21, i_27);
356 | and(i_21, i_22, A);
357 | not(i_22, i_23);
358 | or(i_23, i_24, i_25);
359 | and(i_24, B, CI);
360 | not(i_25, i_26);
361 | or(i_26, B, CI);
362 | not(i_27, i_28);
363 | or(i_28, i_29, A);
364 | not(i_29, i_30);
365 | or(i_30, i_31, i_32);
366 | and(i_31, B, CI);
367 | not(i_32, i_33);
368 | or(i_33, B, CI);
369 | endmodule
370 |
371 | module FILLTIE ();
372 |
373 | endmodule
374 |
375 | module FILL_X1 ();
376 |
377 | endmodule
378 |
379 | module FILL_X2 ();
380 |
381 | endmodule
382 |
383 | module FILL_X4 ();
384 |
385 | endmodule
386 |
387 | module FILL_X8 ();
388 |
389 | endmodule
390 |
391 | module FILL_X16 ();
392 |
393 | endmodule
394 |
395 | module HA_X1 (A, B, CO, S);
396 | input A;
397 | input B;
398 | output CO;
399 | output S;
400 |
401 | and(CO, A, B);
402 | not(S, i_42);
403 | or(i_42, i_43, i_44);
404 | and(i_43, A, B);
405 | not(i_44, i_45);
406 | or(i_45, A, B);
407 | endmodule
408 |
409 | module INV_X1 (I, ZN);
410 | input I;
411 | output ZN;
412 |
413 | not(ZN, I);
414 | endmodule
415 |
416 | module INV_X2 (I, ZN);
417 | input I;
418 | output ZN;
419 |
420 | not(ZN, I);
421 | endmodule
422 |
423 | module INV_X4 (I, ZN);
424 | input I;
425 | output ZN;
426 |
427 | not(ZN, I);
428 | endmodule
429 |
430 | module INV_X8 (I, ZN);
431 | input I;
432 | output ZN;
433 |
434 | not(ZN, I);
435 | endmodule
436 |
437 | module INV_X12 (I, ZN);
438 | input I;
439 | output ZN;
440 |
441 | not(ZN, I);
442 | endmodule
443 |
444 | module INV_X16 (I, ZN);
445 | input I;
446 | output ZN;
447 |
448 | not(ZN, I);
449 | endmodule
450 |
451 | `ifdef PRIMITIVES
452 | primitive \seq_LHQ_X1 (IQ, nextstate, E, NOTIFIER);
453 | output IQ;
454 | input nextstate;
455 | input E;
456 | input NOTIFIER;
457 | reg IQ;
458 |
459 | table
460 | // nextstate E NOTIFIER : @IQ : IQ
461 | 0 1 ? : ? : 0;
462 | 1 1 ? : ? : 1;
463 | * ? ? : ? : -; // Ignore all edges on nextstate
464 | ? 0 ? : ? : -; // Ignore non-triggering clock edge
465 | ? ? * : ? : x; // Any NOTIFIER change
466 | endtable
467 | endprimitive
468 |
469 | module LHQ_X1 (D, E, Q);
470 | input D;
471 | input E;
472 | output Q;
473 | reg NOTIFIER;
474 |
475 | \seq_LHQ_X1 (IQ, nextstate, E, NOTIFIER);
476 | not(IQN, IQ);
477 | buf(Q, IQ);
478 | buf(nextstate, D);
479 | endmodule
480 | `endif
481 |
482 | module MUX2_X1 (I0, I1, S, Z);
483 | input I0;
484 | input I1;
485 | input S;
486 | output Z;
487 |
488 | or(Z, i_12, i_13);
489 | and(i_12, S, I1);
490 | and(i_13, i_14, I0);
491 | not(i_14, S);
492 | endmodule
493 |
494 | module NAND2_X1 (A1, A2, ZN);
495 | input A1;
496 | input A2;
497 | output ZN;
498 |
499 | not(ZN, i_24);
500 | and(i_24, A1, A2);
501 | endmodule
502 |
503 | module NAND2_X2 (A1, A2, ZN);
504 | input A1;
505 | input A2;
506 | output ZN;
507 |
508 | not(ZN, i_18);
509 | and(i_18, A1, A2);
510 | endmodule
511 |
512 | module NAND3_X1 (A1, A2, A3, ZN);
513 | input A1;
514 | input A2;
515 | input A3;
516 | output ZN;
517 |
518 | not(ZN, i_6);
519 | and(i_6, i_7, A3);
520 | and(i_7, A1, A2);
521 | endmodule
522 |
523 | module NAND3_X2 (A1, A2, A3, ZN);
524 | input A1;
525 | input A2;
526 | input A3;
527 | output ZN;
528 |
529 | not(ZN, i_6);
530 | and(i_6, i_7, A3);
531 | and(i_7, A1, A2);
532 | endmodule
533 |
534 | module NAND4_X1 (A1, A2, A3, A4, ZN);
535 | input A1;
536 | input A2;
537 | input A3;
538 | input A4;
539 | output ZN;
540 |
541 | not(ZN, i_0);
542 | and(i_0, i_1, A4);
543 | and(i_1, i_2, A3);
544 | and(i_2, A1, A2);
545 | endmodule
546 |
547 | module NAND4_X2 (A1, A2, A3, A4, ZN);
548 | input A1;
549 | input A2;
550 | input A3;
551 | input A4;
552 | output ZN;
553 |
554 | not(ZN, i_0);
555 | and(i_0, i_1, A4);
556 | and(i_1, i_2, A3);
557 | and(i_2, A1, A2);
558 | endmodule
559 |
560 | module NOR2_X1 (A1, A2, ZN);
561 | input A1;
562 | input A2;
563 | output ZN;
564 |
565 | not(ZN, i_12);
566 | or(i_12, A1, A2);
567 | endmodule
568 |
569 | module NOR2_X2 (A1, A2, ZN);
570 | input A1;
571 | input A2;
572 | output ZN;
573 |
574 | not(ZN, i_12);
575 | or(i_12, A1, A2);
576 | endmodule
577 |
578 | module NOR3_X1 (A1, A2, A3, ZN);
579 | input A1;
580 | input A2;
581 | input A3;
582 | output ZN;
583 |
584 | not(ZN, i_0);
585 | or(i_0, i_1, A3);
586 | or(i_1, A1, A2);
587 | endmodule
588 |
589 | module NOR3_X2 (A1, A2, A3, ZN);
590 | input A1;
591 | input A2;
592 | input A3;
593 | output ZN;
594 |
595 | not(ZN, i_0);
596 | or(i_0, i_1, A3);
597 | or(i_1, A1, A2);
598 | endmodule
599 |
600 | module NOR4_X1 (A1, A2, A3, A4, ZN);
601 | input A1;
602 | input A2;
603 | input A3;
604 | input A4;
605 | output ZN;
606 |
607 | not(ZN, i_0);
608 | or(i_0, i_1, A4);
609 | or(i_1, i_2, A3);
610 | or(i_2, A1, A2);
611 | endmodule
612 |
613 | module NOR4_X2 (A1, A2, A3, A4, ZN);
614 | input A1;
615 | input A2;
616 | input A3;
617 | input A4;
618 | output ZN;
619 |
620 | not(ZN, i_0);
621 | or(i_0, i_1, A4);
622 | or(i_1, i_2, A3);
623 | or(i_2, A1, A2);
624 | endmodule
625 |
626 | module OAI21_X1 (A1, A2, B, ZN);
627 | input A1;
628 | input A2;
629 | input B;
630 | output ZN;
631 |
632 | not(ZN, i_0);
633 | and(i_0, i_1, B);
634 | or(i_1, A1, A2);
635 | endmodule
636 |
637 | module OAI21_X2 (A1, A2, B, ZN);
638 | input A1;
639 | input A2;
640 | input B;
641 | output ZN;
642 |
643 | not(ZN, i_0);
644 | and(i_0, i_1, B);
645 | or(i_1, A1, A2);
646 | endmodule
647 |
648 | module OAI22_X1 (A1, A2, B1, B2, ZN);
649 | input A1;
650 | input A2;
651 | input B1;
652 | input B2;
653 | output ZN;
654 |
655 | not(ZN, i_0);
656 | and(i_0, i_1, i_2);
657 | or(i_1, A1, A2);
658 | or(i_2, B1, B2);
659 | endmodule
660 |
661 | module OAI22_X2 (A1, A2, B1, B2, ZN);
662 | input A1;
663 | input A2;
664 | input B1;
665 | input B2;
666 | output ZN;
667 |
668 | not(ZN, i_0);
669 | and(i_0, i_1, i_2);
670 | or(i_1, A1, A2);
671 | or(i_2, B1, B2);
672 | endmodule
673 |
674 | module OR2_X1 (A1, A2, Z);
675 | input A1;
676 | input A2;
677 | output Z;
678 |
679 | or(Z, A1, A2);
680 | endmodule
681 |
682 | module OR2_X2 (A1, A2, Z);
683 | input A1;
684 | input A2;
685 | output Z;
686 |
687 | or(Z, A1, A2);
688 | endmodule
689 |
690 | module OR3_X1 (A1, A2, A3, Z);
691 | input A1;
692 | input A2;
693 | input A3;
694 | output Z;
695 |
696 | or(Z, i_0, A3);
697 | or(i_0, A1, A2);
698 | endmodule
699 |
700 | module OR3_X2 (A1, A2, A3, Z);
701 | input A1;
702 | input A2;
703 | input A3;
704 | output Z;
705 |
706 | or(Z, i_0, A3);
707 | or(i_0, A1, A2);
708 | endmodule
709 |
710 | module OR4_X1 (A1, A2, A3, A4, Z);
711 | input A1;
712 | input A2;
713 | input A3;
714 | input A4;
715 | output Z;
716 |
717 | or(Z, i_0, A4);
718 | or(i_0, i_1, A3);
719 | or(i_1, A1, A2);
720 | endmodule
721 |
722 | module OR4_X2 (A1, A2, A3, A4, Z);
723 | input A1;
724 | input A2;
725 | input A3;
726 | input A4;
727 | output Z;
728 |
729 | or(Z, i_0, A4);
730 | or(i_0, i_1, A3);
731 | or(i_1, A1, A2);
732 | endmodule
733 |
734 | `ifdef PRIMITIVES
735 | primitive \seq_SDFFRNQ_X1 (IQ, RN, nextstate, CLK, NOTIFIER);
736 | output IQ;
737 | input RN;
738 | input nextstate;
739 | input CLK;
740 | input NOTIFIER;
741 | reg IQ;
742 |
743 | table
744 | // RN nextstate CLK NOTIFIER : @IQ : IQ
745 | ? 0 r ? : ? : 0;
746 | 1 1 r ? : ? : 1;
747 | ? 0 * ? : 0 : 0; // reduce pessimism
748 | 1 1 * ? : 1 : 1; // reduce pessimism
749 | 1 * ? ? : ? : -; // Ignore all edges on nextstate
750 | 1 ? n ? : ? : -; // Ignore non-triggering clock edge
751 | 0 ? ? ? : ? : 0; // RN activated
752 | * ? ? ? : 0 : 0; // Cover all transitions on RN
753 | ? ? ? * : ? : x; // Any NOTIFIER change
754 | endtable
755 | endprimitive
756 |
757 | module SDFFRNQ_X1 (D, RN, SE, SI, CLK, Q);
758 | input D;
759 | input RN;
760 | input SE;
761 | input SI;
762 | input CLK;
763 | output Q;
764 | reg NOTIFIER;
765 |
766 | \seq_SDFFRNQ_X1 (IQ, RN, nextstate, CLK, NOTIFIER);
767 | not(IQN, IQ);
768 | buf(Q, IQ);
769 | or(nextstate, i_0, i_1);
770 | and(i_0, SE, SI);
771 | and(i_1, i_2, D);
772 | not(i_2, SE);
773 | endmodule
774 |
775 | primitive \seq_SDFFSNQ_X1 (IQ, SN, nextstate, CLK, NOTIFIER);
776 | output IQ;
777 | input SN;
778 | input nextstate;
779 | input CLK;
780 | input NOTIFIER;
781 | reg IQ;
782 |
783 | table
784 | // SN nextstate CLK NOTIFIER : @IQ : IQ
785 | 1 0 r ? : ? : 0;
786 | ? 1 r ? : ? : 1;
787 | 1 0 * ? : 0 : 0; // reduce pessimism
788 | ? 1 * ? : 1 : 1; // reduce pessimism
789 | 1 * ? ? : ? : -; // Ignore all edges on nextstate
790 | 1 ? n ? : ? : -; // Ignore non-triggering clock edge
791 | 0 ? ? ? : ? : 1; // SN activated
792 | * ? ? ? : 1 : 1; // Cover all transitions on SN
793 | ? ? ? * : ? : x; // Any NOTIFIER change
794 | endtable
795 | endprimitive
796 |
797 | module SDFFSNQ_X1 (D, SE, SI, SN, CLK, Q);
798 | input D;
799 | input SE;
800 | input SI;
801 | input SN;
802 | input CLK;
803 | output Q;
804 | reg NOTIFIER;
805 |
806 | \seq_SDFFSNQ_X1 (IQ, SN, nextstate, CLK, NOTIFIER);
807 | not(IQN, IQ);
808 | buf(Q, IQ);
809 | or(nextstate, i_0, i_1);
810 | and(i_0, SE, SI);
811 | and(i_1, i_2, D);
812 | not(i_2, SE);
813 | endmodule
814 | `endif
815 |
816 | module TBUF_X1 (EN, I, Z);
817 | input EN;
818 | input I;
819 | output Z;
820 |
821 | bufif0(Z, Z_in, Z_enable);
822 | not(Z_enable, EN);
823 | buf(Z_in, I);
824 | endmodule
825 |
826 | module TBUF_X2 (EN, I, Z);
827 | input EN;
828 | input I;
829 | output Z;
830 |
831 | bufif0(Z, Z_in, Z_enable);
832 | not(Z_enable, EN);
833 | buf(Z_in, I);
834 | endmodule
835 |
836 | module TBUF_X4 (EN, I, Z);
837 | input EN;
838 | input I;
839 | output Z;
840 |
841 | bufif0(Z, Z_in, Z_enable);
842 | not(Z_enable, EN);
843 | buf(Z_in, I);
844 | endmodule
845 |
846 | module TBUF_X8 (EN, I, Z);
847 | input EN;
848 | input I;
849 | output Z;
850 |
851 | bufif0(Z, Z_in, Z_enable);
852 | not(Z_enable, EN);
853 | buf(Z_in, I);
854 | endmodule
855 |
856 | module TBUF_X12 (EN, I, Z);
857 | input EN;
858 | input I;
859 | output Z;
860 |
861 | bufif0(Z, Z_in, Z_enable);
862 | not(Z_enable, EN);
863 | buf(Z_in, I);
864 | endmodule
865 |
866 | module TBUF_X16 (EN, I, Z);
867 | input EN;
868 | input I;
869 | output Z;
870 |
871 | bufif0(Z, Z_in, Z_enable);
872 | not(Z_enable, EN);
873 | buf(Z_in, I);
874 | endmodule
875 |
876 | module TIEH (Z);
877 | output Z;
878 |
879 | buf(Z, 1);
880 | endmodule
881 |
882 | module TIEL (ZN);
883 | output ZN;
884 |
885 | buf(ZN, 0);
886 | endmodule
887 |
888 | module XNOR2_X1 (A1, A2, ZN);
889 | input A1;
890 | input A2;
891 | output ZN;
892 |
893 | not(ZN, i_18);
894 | not(i_18, i_19);
895 | or(i_19, i_20, i_21);
896 | and(i_20, A1, A2);
897 | not(i_21, i_22);
898 | or(i_22, A1, A2);
899 | endmodule
900 |
901 | module XOR2_X1 (A1, A2, Z);
902 | input A1;
903 | input A2;
904 | output Z;
905 |
906 | not(Z, i_18);
907 | or(i_18, i_19, i_20);
908 | and(i_19, A1, A2);
909 | not(i_20, i_21);
910 | or(i_21, A1, A2);
911 | endmodule
912 |
913 | `ifdef PRIMITIVES
914 | `ifdef TETRAMAX
915 | `else
916 | primitive ng_xbuf (o, i, d);
917 | output o;
918 | input i, d;
919 | table
920 | // i d : o
921 | 0 1 : 0 ;
922 | 1 1 : 1 ;
923 | x 1 : 1 ;
924 | endtable
925 | endprimitive
926 | `endif
927 | `endif
928 | //
929 | // End of file
930 | //
931 |
--------------------------------------------------------------------------------
/templates/resynth.ys:
--------------------------------------------------------------------------------
1 | read_verilog [[RTLFILENAME]]
2 | read_verilog [[TECHNOLOGY]]
3 | hierarchy -top [[TOPMODULE]]
4 | prep; flatten; synth
5 | clean -purge
6 |
7 | dfflibmap -liberty [[LIBRARY]]
8 | clean -purge
9 | abc -liberty [[LIBRARYABC]] -script +strash;ifraig;scorr;dc2;dretime;retime,{D};strash;&get,-n;&nf,{D};&put
10 | clean -purge
11 |
12 | write_verilog -noattr -noexpr [[NETLIST]]
13 |
--------------------------------------------------------------------------------
/templates/stat.ys:
--------------------------------------------------------------------------------
1 | read_verilog [[RTLFILENAME]]
2 | read_verilog [[TECHNOLOGY]]
3 | hierarchy -top [[TOPMODULE]]
4 |
5 | stat -liberty [[LIBRARY]]
6 |
7 |
--------------------------------------------------------------------------------
/templates/synth.ys:
--------------------------------------------------------------------------------
1 | read_verilog [[RTLFILENAME]]
2 | hierarchy -top [[TOPMODULE]]
3 | prep; flatten; synth
4 | clean -purge
5 |
6 | dfflibmap -liberty [[LIBRARY]]
7 | clean -purge
8 | abc -liberty [[LIBRARYABC]] -script +strash;ifraig;scorr;dc2;dretime;retime,{D};strash;&get,-n;&nf,{D};&put
9 | clean -purge
10 |
11 | write_verilog -noattr -noexpr [[NETLIST]]
12 |
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 |
2 | import unittest
3 |
4 | from circuit import Circuit
5 | from pruning_algorithms.axls import GetInputs, GetOutputs
6 | from pruning_algorithms.probprun import GetOneNode
7 |
8 | RTL='circuits/brent.kung.16b/UBBKA_15_0_15_0.v'
9 | SAIF='circuits/brent.kung.16b/UBBKA_15_0_15_0.saif'
10 |
11 | CIRCUIT_INPUTS = [ "X[15]", "X[14]", "X[13]", "X[12]", "X[11]", "X[10]", "X[9]", "X[8]", "X[7]", "X[6]", "X[5]", "X[4]", "X[3]", "X[2]", "X[1]", "X[0]",
12 | "Y[15]", "Y[14]", "Y[13]", "Y[12]", "Y[11]", "Y[10]", "Y[9]", "Y[8]", "Y[7]", "Y[6]", "Y[5]", "Y[4]", "Y[3]", "Y[2]", "Y[1]", "Y[0]" ]
13 | CIRCUIT_OUTPUTS = [ "S[16]", "S[14]", "S[13]", "S[12]", "S[11]", "S[10]", "S[9]", "S[8]", "S[7]", "S[6]", "S[5]", "S[4]", "S[3]", "S[2]", "S[1]", "S[0]" ]
14 |
15 | class PoisonoakTest(unittest.TestCase):
16 |
17 | def test_circuit_inputs(self):
18 | self.assertEqual(
19 | self.our_circuit.inputs,
20 | CIRCUIT_INPUTS,
21 | f"Should be {CIRCUIT_INPUTS}"
22 | )
23 |
24 | def test_circuit_outputs(self):
25 | self.assertEqual(
26 | self.our_circuit.outputs,
27 | CIRCUIT_OUTPUTS,
28 | f"Should be {CIRCUIT_OUTPUTS}"
29 | )
30 |
31 | def setUp(self):
32 | self.our_circuit = Circuit(RTL, "NanGate15nm", SAIF)
33 | # print(our_circuit.get_circuit_xml())
34 | # our_circuit.show()
35 |
36 | if __name__ == '__main__':
37 | unittest.main()
38 |
39 |
40 | '''
41 | # the root of the xml tree is at:
42 | print(our_circuit.netl_root)
43 |
44 |
45 | # lets delete a node for example the _101_
46 | node101 = our_circuit.netl_root.find("./node[@var='_101_']")
47 | node101.set("delete", "yes")
48 |
49 | # or
50 | our_circuit.delete("_101_")
51 |
52 | # lets plot the circuit showing the nodes to be deleted
53 | our_circuit.show(show_deletes=True)
54 |
55 | # lets write the new netlist without the deleted node
56 | our_circuit.write_to_disk(filename="netlist_without_101.v")
57 |
58 | # Lets start with our pruning algorithms
59 |
60 | # Extracts the nodes that can be deleted if inputs of bit 0 are constants
61 | inputs = ["X[0]","Y[0]"]
62 | depricable_nodes = GetInputs(our_circuit.netl_root, inputs)
63 | print(depricable_nodes)
64 | print("Nodes to delete if input 0 is constant")
65 | print([ n.attrib["var"] for n in depricable_nodes ])
66 |
67 | # Extracts the nodes that can be deleted if inputs of bit 3 are constants
68 | inputs = ["X[0]","Y[0]","X[1]","Y[1]","X[2]","Y[2]","X[3]","Y[3]"]
69 | depricable_nodes = GetInputs(our_circuit.netl_root, inputs)
70 | print("Nodes to delete if input 3 is constant")
71 | print([ n.attrib["var"] for n in depricable_nodes ])
72 |
73 |
74 | # Extracts the nodes that can be deleted if output of bit 0 is constant
75 | outputs = ["S[0]"]
76 | depricable_nodes = GetOutputs(our_circuit.netl_root, outputs)
77 | print(depricable_nodes)
78 | print("Nodes to delete if output 0 is constant")
79 | print([ n.attrib["var"] for n in depricable_nodes ])
80 |
81 | # Extracts the nodes that can be deleted if outputs of bit 3 is constant
82 | outputs = ["S[5]"]
83 | depricable_nodes = GetOutputs(our_circuit.netl_root, outputs)
84 | print("Nodes to delete if output 5 is constant")
85 | print([ n.attrib["var"] for n in depricable_nodes ])
86 |
87 | # Lets try another method: pseudo probprub.
88 | # Every time we call the function it returns the recommended node to delete
89 | # because it was all the time in 1 or 0
90 |
91 | pseudo_probprun = GetOneNode(our_circuit.netl_root)
92 |
93 | node, output, time = next(pseudo_probprun)
94 |
95 | print(f"ProbPrun suggest delete the node {node} because is {output} {time}% of the time")
96 |
97 | # so lets take out the 10 most useless nodes
98 |
99 | for x in range (10):
100 | node, output, time = next(pseudo_probprun)
101 | print(f"{node} is {output} {time}% of the time")
102 | '''
103 |
104 |
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 |
2 | from datetime import datetime
3 | import random
4 | import string
5 | import numpy as np
6 | import math
7 | from random import uniform, gauss, triangular
8 |
9 | def get_name(length):
10 | timestamp = datetime.now().strftime("%H%M%S")
11 | unique = ""
12 | for _ in range(length):
13 | unique += random.choice(string.ascii_letters)
14 | return f"{timestamp}{unique}"
15 |
16 | def get_random(bits: int, distribution='uniform', samples=1, **kwargs):
17 | '''
18 |
19 | Generates samples of integer randomly distributed data.
20 |
21 | Parameters
22 | ----------
23 | bits: int
24 | Number of bits for the integer data.
25 | distribution: string
26 | Name of the desired random distribution. Could be:
27 | "gaussian" or "normal" for a normal distribution.
28 | "uniform" or "rectangular" for a uniform distribution.
29 | "triangular" for a triangular distribution.
30 | TODO: Add more distributions
31 | samples: int
32 | Number of samples.
33 |
34 | **kwargs (optional)
35 | median: int
36 | The center of the distribution (works only for certain distributions)
37 | std: int
38 | Standard deviation of the destribution (only gaussian/normal distribution)
39 | limits: int tuple
40 | Lower and upper limit of the dataset. By default it takes the whole range of numbers: [0,2^n-1]
41 |
42 | Returns
43 | -------
44 | data
45 | Randomized data sampled from the specified random distribution
46 |
47 | '''
48 | '''Pasing kwargs'''
49 | if 'low_limit' not in kwargs:
50 | low_limit=0 #Lower threshold for generated numbers
51 | else:
52 | low_limit=np.min([kwargs['low_limit'],2**bits])
53 | if 'high_limit' not in kwargs:
54 | high_limit=2**bits #Upper threshold for the generated data
55 | else:
56 | high_limit=np.min([kwargs['high_limit'],2**bits])
57 | if 'median' not in kwargs:
58 | median=(high_limit+low_limit)/2 #by default is centered at the mean
59 | else:
60 | median=kwargs['median']
61 | if 'variance' not in kwargs:
62 | variance=1
63 | else:
64 | variance=kwargs['variance']
65 |
66 | '''Distributions case'''
67 | data=[]
68 | if distribution in {'uniform', 'rectangular'}:
69 | data=(int(math.floor(uniform(low_limit,high_limit))) for _ in range(samples))
70 | elif distribution=='triangular':
71 | data=(int(math.floor(triangular(low_limit,high_limit,mode=median))) for _ in range(samples))
72 | elif distribution in {'normal', 'gaussian'}:
73 | while len(data)