├── .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 | A diagram giving a rough overview of the steps explained below. 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)