diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index c196994c..bc4e9391 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -5,7 +5,7 @@ on: [push] jobs: SpyDrNet_push: name: SpyDrNet Push - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: python-version: @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@master - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Run Pytest for SpyDrNet diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ce7ec45c..00000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ - -language: python -python: - - "3.6" - - "3.7" - - "3.8" - - "nightly" - - "3.9" -install: - - pip install ply -script: - - pytest \ No newline at end of file diff --git a/RELEASE.rst b/RELEASE.rst index ed4619b5..47d70ce6 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,3 +1,29 @@ +SpyDrNet 1.12.2 +---------------- +April 18, 2023 + +Bug fix for Verilog parser for partially connected ports being misaligned and fixed primitive name with a space at the end. +Changed some of the os.path to pathlib to conform to updated coding standards + +SpyDrNet 1.12.1 +---------------- +February 2, 2023 + +* Bug fix for names in EDIF netlists in which they were cut off if more than 100 characters long. Now the allowed number of characters in a name is 255. +* Slight additions to documentation about doing a release +* Removing Travis CI (from now on only Github Actions will do automated testing) +* Fixed bug where architecture liraries were not included in the release + +SpyDrNet 1.12.0 +---------------- +November 9, 2022 + +* Major improvements and fixes for the Verilog netlist parser and composer to broaden support and accuracy +* Built in primitive libraries to allow SpyDrNet to populate port directions of primitives when parsing netlist types that don't always explicitly define primitive cells (Verilog, EBLIF). An option is added to parsing to specify which primitive library to use. +* Improvements, fixes, and simplification to EBLIF netlist parser and composer +* Verilog parser and composer option to remove/add lagging space in names +* Documentation updates and improvements + SpyDrNet 1.11.1 --------------- April 20, 2022 diff --git a/docs/source/conf.py b/docs/source/conf.py index e8f550db..e9a23837 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -77,7 +77,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -164,7 +164,7 @@ 'BYU Configurable Computing Lab', 'manual'), ] -latex_appendices = ['tutorial'] +latex_appendices = ['overview/tutorial','overview/start'] # -- Options for manual page output ------------------------------------------ diff --git a/docs/source/developer/contribute.rst b/docs/source/developer/contribute.rst index 7aa0a634..8be9e0d2 100644 --- a/docs/source/developer/contribute.rst +++ b/docs/source/developer/contribute.rst @@ -8,14 +8,14 @@ This section aims to help you do a spydrnet release, meaning you make the latest 1. Merge each contributors' branches into one branch (the 'next_release' branch) 2. run `git merge master` while the next-release branch is checked out. -3. :ref:`update_release_notes` +3. :ref:`update_release_notes` (remember to commit and push afterwards). 4. :ref:`update_documentation` and ensure it can build properly 5. On Github, create a pull request with the updated code in the next_release branch. 6. Accept and merge the pull request when the checks have finished. 7. Move to the master branch using `git checkout master` 8. :ref:`create_and_update_tag` -9. :ref:`build_documentation` -10. :ref:`build_package` (this will update the documentation’s version number) +9. :ref:`build_package` (this will update the documentation's version number) +10. :ref:`build_documentation` 11. :ref:`publish_packages` 12. :ref:`github_release` 13. :ref:`publish_documentation` @@ -90,23 +90,6 @@ If you mess up, you can use the following instructions to force update your tag >>> git push --tags -f -.. _build_documentation: - -Build the Documentation ------------------------- - -Make sure you are in the docs directory - ->>> cd docs - -then run the following: - ->>> make clean ->>> make latexpdf ->>> make html - -Make sure that each one executes without errors. Try to minimize warnings as well, although the most important thing is that the documentation looks the way you want it to. - .. _build_package: Build the Python Package @@ -130,6 +113,23 @@ The build files will be stored in the following directories spydrnet/build and spydrnet/dist +.. _build_documentation: + +Build the Documentation +------------------------ + +Make sure you are in the docs directory + +>>> cd docs + +then run the following: + +>>> make clean +>>> make latexpdf +>>> make html + +Make sure that each one executes without errors. Try to minimize warnings as well, although the most important thing is that the documentation looks the way you want it to. + .. _publish_packages: Publish the Packages to Pypi diff --git a/docs/source/index.rst b/docs/source/index.rst index fc2cc822..251856c1 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -12,7 +12,7 @@ :maxdepth: 2 install - tutorial + overview/index reference/index auto_examples/index developer/index diff --git a/docs/source/overview/index.rst b/docs/source/overview/index.rst new file mode 100644 index 00000000..74bd8261 --- /dev/null +++ b/docs/source/overview/index.rst @@ -0,0 +1,10 @@ +.. _overview: + +Tutorial +******** + +.. toctree:: + :maxdepth: 1 + + tutorial + start \ No newline at end of file diff --git a/docs/source/overview/start.rst b/docs/source/overview/start.rst new file mode 100644 index 00000000..4daed726 --- /dev/null +++ b/docs/source/overview/start.rst @@ -0,0 +1,88 @@ +Getting Started with SpyDrNet +============================= + +SpyDrNet was originally built for EDF netlist, but has since been expanded to Verilog and EBLIF netlists. + +* Note + This guide makes references to SpyDrNet TMR + +Parsing +------- + +**Default parsing arguments** + +``parse(filename, architecture=None):`` + +filename + +- Name of the file that is being parsed + +architecture + +- Desired board architecture + + +**General structure** + +``netlist = sdn.parse(filename, ...)`` + +**Basic structure for Verilog netlist** + +``netlist = sdn.parse("filename.v", architecture=XILINX_7SERIES)`` + +**Basic structure for EDF netlists** + +``netlist = sdn.parse("filename.edf")`` + +*edf usually only needs the filename and not the other arguments* + +Composing +---------- + +**Default composing arguments** + +``compose(netlist, filename, voters=[], definition_list=[], write_blackbox=True, write_eblif_cname=True, defparam=False):`` + +netlist + +- Netlist that was parsed in / replicated / changed + +filename + +- Desired output name of netlist + +voters + +- List of voters that was created in script *this is only needed for verilog netlists* + +definition_list + +- List of definitions to write + +write_blackbox + +- Flag that skips writing black boxes/verilog primitives + +write_eblif_cname + +- Flag + +defparam + +- Flag that composes parameters in *defparam* statements instead of using #() + + +**General structure** + +``netlist.compose(filename, ...)`` + +**Basic structure for Verilog netlist** + +``netlist.compose("filename_tmr.v", voters, reinsert_space=True)`` + +**Basic structure for EDF netlists** + +``netlist.compose("filename_tmr.edf")`` + +*edf usually only needs the filename and not the other arguments* + diff --git a/docs/source/tutorial.rst b/docs/source/overview/tutorial.rst similarity index 98% rename from docs/source/tutorial.rst rename to docs/source/overview/tutorial.rst index 75f795ff..dc6afdc5 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/overview/tutorial.rst @@ -1,10 +1,10 @@ -Tutorial -============ +Overview +======== SpyDrNet is a tool for the analysis and transformation of structural netlists. A structural netlist is a static representation of an electronic circuit. A circuit consists of a number of electrical components and their connections. Figure :numref:`fig:hierarchical_netlist` shows a graphical representation of a netlist. .. _fig:hierarchical_netlist: -.. figure:: figures/hierarchical_netlist.* +.. figure:: ../figures/hierarchical_netlist.* :align: center Hierarchical representation of a Netlist @@ -24,7 +24,7 @@ This flow is inspired by `LLVM`_ and `Pandoc`_. LLVM has a similar flow for comp .. _Pandoc: https://pandoc.org/ .. _fig:flow.2: -.. figure:: /figures/flow.* +.. figure:: ../figures/flow.* :align: center :alt: SpyDrNet Flow @@ -177,14 +177,14 @@ Instances: **See the following two figures to aid in understanding the SpyDrNet IR:** .. _fig:IR_2: -.. figure:: /figures/IR.* +.. figure:: ../figures/IR.* :align: center :alt: SpyDrNet Intermediate Representation Summary of the SpyDrNet IR 1 .. _fig:IR_3: -.. figure:: /figures/spydrnet_api_elements.png +.. figure:: ../figures/spydrnet_api_elements.png :align: center :alt: SpyDrNet Intermediate Representation diff --git a/docs/source/reference/archive/index.rst b/docs/source/reference/archive/index.rst new file mode 100644 index 00000000..1314e6fb --- /dev/null +++ b/docs/source/reference/archive/index.rst @@ -0,0 +1,12 @@ +.. _archive: + +=========== +Archive +=========== + +The following is from older versions of the SpyDrNet documentation + +.. toctree:: + :maxdepth: 2 + + introduction \ No newline at end of file diff --git a/docs/source/reference/introduction.rst b/docs/source/reference/archive/introduction.rst similarity index 99% rename from docs/source/reference/introduction.rst rename to docs/source/reference/archive/introduction.rst index e2555a27..474fed25 100644 --- a/docs/source/reference/introduction.rst +++ b/docs/source/reference/archive/introduction.rst @@ -47,7 +47,7 @@ Netlists flow through SpyDrNet in a three step process (see :numref:`fig:flow`). .. _Pandoc: https://pandoc.org/ .. _fig:flow: -.. figure:: ../figures/flow.* +.. figure:: ../../figures/flow.* :align: center :alt: SpyDrNet Flow @@ -82,7 +82,7 @@ At this point, SpyDrNet features and functionality are accessible via ``sdn. 1: for i in range(len(port.pins)): to_write+=port.name+"["+str(i)+"] " @@ -66,7 +76,7 @@ def compose_top_level_ports(self): self.write_out(to_write) to_write = ".outputs " - for port in self.netlist.top_instance.get_ports(filter = lambda x: x.direction is sdn.Port.Direction.OUT): + for port in self.current_model.get_ports(filter = lambda x: x.direction in {sdn.OUT, sdn.INOUT}): if len(port.pins) > 1: for i in range(len(port.pins)): to_write+=port.name+"["+str(i)+"] " @@ -75,28 +85,13 @@ def compose_top_level_ports(self): to_write+="\n" self.write_out(to_write) - def compose_top_level_clocks(self): - if "EBLIF.clock" in self.netlist.top_instance.data: + def compose_model_clocks(self): + if "EBLIF.clock" in self.current_model.data: to_write = ".clock " - for clock in self.netlist.top_instance["EBLIF.clock"]: + for clock in self.current_model["EBLIF.clock"]: to_write+=clock+" " self.write_out(to_write+"\n") - def compose_default_wires(self): - default_wires = list() - try: - self.netlist["EBLIF.default_wires"] - default_wires = self.netlist['EBLIF.default_wires'] - except KeyError: - None - if "$false" in default_wires: - self.write_out(".names $false\n") - if "$true" in default_wires: - self.write_out(".names $true\n1\n") - if "$undef" in default_wires: - self.write_out(".names $undef\n") - self.write_out("\n") - def compose_instances(self): categories = self.separate_by_type() if "EBLIF.subckt" in categories.keys(): @@ -112,7 +107,7 @@ def compose_instances(self): def separate_by_type(self): dict_by_types = dict() - for instance in self.netlist.get_instances(): + for instance in self.current_model.children: try: instance["EBLIF.type"] except KeyError: @@ -128,7 +123,8 @@ def separate_by_type(self): def compose_subcircuits(self,list_of_subcircuits,is_gate=False): for subckt in list_of_subcircuits: - self.blackboxes_to_compose.add(subckt.reference.name) + if (subckt.is_leaf()): + self.blackboxes_to_compose.add(subckt.reference) to_add = "" if is_gate: to_write = ".gate "+ subckt.reference.name+" " @@ -142,7 +138,7 @@ def compose_subcircuits(self,list_of_subcircuits,is_gate=False): inner_pin_list = port.pins for pin in subckt.get_pins(selection=Selection.OUTSIDE,filter=lambda x: x.inner_pin.port is port): if (amount_of_ports_to_write > 5): - to_write+=to_add+" \\ \n" + to_write+=to_add+" \\\n" to_add = "" if len(inner_pin_list) > 1: index = inner_pin_list.index(pin.inner_pin) @@ -193,20 +189,15 @@ def compose_names(self,list_of_names): def compose_latches(self,latch_list): for latch_instance in latch_list: to_write = ".latch " - # port_list = list(x for x in latch_instance.get_ports()) for port_type in ['input', 'output', 'type', 'control', 'init-val']: # this is the specific order of ports - # current_port = next(port for port in port_list if port.name == port_type) for pin in latch_instance.get_pins(selection=Selection.OUTSIDE,filter=lambda x: x.inner_pin.port.name == port_type): - # connection_name = None if pin.wire: to_write+=pin.wire.cable.name if (len(pin.wire.cable.wires)>1): to_write+="["+str(pin.wire.index())+"]" to_write+=" " - # connection_name=pin.wire.cable.name else: to_write+="unconn " - # connection_name="unconn" to_write+='\n' self.write_out(to_write) self.find_and_write_additional_instance_info(latch_instance) @@ -222,9 +213,6 @@ def find_connected_wire_info(self,pin): def find_and_write_additional_instance_info(self,instance): to_write = "" if self.write_cname: - # if "EBLIF.cname" in instance.data: - # to_write+=".cname "+instance["EBLIF.cname"]+'\n' - # else: to_write+=".cname "+instance.name+'\n' if "EBLIF.attr" in instance.data: for key, value in instance.data["EBLIF.attr"].items(): @@ -235,19 +223,19 @@ def find_and_write_additional_instance_info(self,instance): self.write_out(to_write) def compose_blackboxes(self): - for definition in self.netlist.get_definitions(): - if definition.name in self.blackboxes_to_compose: - if "EBLIF.blackbox" in definition.data.keys(): - to_write = "\n.model "+definition.name - to_write+="\n.inputs" - for port in definition.get_ports(filter=lambda x: x.direction is sdn.IN): - to_write+=" "+port.name - to_write+="\n.outputs" - for port in definition.get_ports(filter=lambda x: x.direction is sdn.OUT): - to_write+=" "+port.name - self.write_out(to_write+"\n") - self.write_out(".blackbox\n") - self.compose_end() + primitive_library = next(self.netlist.get_libraries("hdi_primitives")) + for definition in primitive_library.definitions: + if definition in self.blackboxes_to_compose and "logic-gate" not in definition.name: # only compose blackboxes that are not .names + to_write = ".model " + definition.name + to_write+="\n.inputs" + for port in definition.get_ports(filter=lambda x: x.direction is sdn.IN): + to_write+=" "+port.name + to_write+="\n.outputs" + for port in definition.get_ports(filter=lambda x: x.direction is sdn.OUT): + to_write+=" "+port.name + self.write_out(to_write+"\n") + self.write_out(".blackbox\n") + self.compose_end() def compose_end(self): - self.write_out(".end\n") + self.write_out(".end\n\n") diff --git a/spydrnet/composers/eblif/tests/test_eblif_composer.py b/spydrnet/composers/eblif/tests/test_eblif_composer.py index b71bbf63..d688427c 100644 --- a/spydrnet/composers/eblif/tests/test_eblif_composer.py +++ b/spydrnet/composers/eblif/tests/test_eblif_composer.py @@ -2,6 +2,7 @@ import os import spydrnet as sdn from spydrnet import base_dir +from pathlib import Path """ Test the EBLIF composer. The best way I can think to do this is to parse a netlist, compose it, then parse it again to see if anything changed. It should all be the same @@ -9,7 +10,7 @@ class TestEBLIFComposer(unittest.TestCase): def setUp(self): - self.netlist_1 = sdn.parse(os.path.join(base_dir, 'support_files', 'eblif_netlists', "toggle.eblif.zip")) + self.netlist_1 = sdn.parse(Path(base_dir, 'support_files', 'eblif_netlists', "toggle.eblif.zip")) self.definition_list = ["INV","BUFG","FDRE","IBUF","OBUF","toggle", "logic-gate_0"] sdn.compose(self.netlist_1,"temp_for_composer_test.eblif") sdn.compose(self.netlist_1,"temp_for_composer_test_no_blackbox.eblif",write_blackbox=False) @@ -47,13 +48,9 @@ def test_cables(self): cables_2.sort() self.assertEqual(cables_1,cables_2) self.assertEqual(len(cables_1),len(cables_2)) - - def test_no_blackbox_netlist(self): - for definition in self.netlist_1.get_definitions(filter=lambda x: (x is not self.netlist_1.top_instance.reference - and "logic-gate_0" not in x.name)): - self.assertTrue("EBLIF.blackbox" in definition.data.keys(),definition.name+" is not a blackbox"+str(definition.data)) - for definition in self.netlist_3.get_definitions(filter=lambda x: x is not self.netlist_3.top_instance.reference): - self.assertFalse("EBLIF.blackbox" in definition.data,definition.name+" is a blackbox"+str(definition.data)) + # TODO add wires and connections tests + + # test the composing of each individual part \ No newline at end of file diff --git a/spydrnet/composers/edif/composer.py b/spydrnet/composers/edif/composer.py index 9ba73a2f..da21de88 100644 --- a/spydrnet/composers/edif/composer.py +++ b/spydrnet/composers/edif/composer.py @@ -129,7 +129,7 @@ def iterate(o): def _add_rename_property(self, obj, namespace_list, rename_helper): if obj.name is None: - raise Exception('obj, ', obj, '.name undifined') + raise Exception('obj, ', obj, '.name undefined') name = obj.name if "EDIF.identifier" in obj.data: return diff --git a/spydrnet/composers/edif/edifify_names.py b/spydrnet/composers/edif/edifify_names.py index b03422a3..16094602 100644 --- a/spydrnet/composers/edif/edifify_names.py +++ b/spydrnet/composers/edif/edifify_names.py @@ -63,7 +63,7 @@ class EdififyNames: def __init__(self): self.valid = set() self.non_alpha = set() - self.name_length_target = 100 + self.name_length_target = 256 def _length_good(self, identifier): """returns a boolean indicating whether or not the indentifier fits the 256 character limit""" diff --git a/spydrnet/composers/verilog/composer.py b/spydrnet/composers/verilog/composer.py index 879789a8..69f48fe6 100644 --- a/spydrnet/composers/verilog/composer.py +++ b/spydrnet/composers/verilog/composer.py @@ -2,18 +2,20 @@ from spydrnet.ir import Port from spydrnet.ir import Cable import spydrnet.parsers.verilog.verilog_tokens as vt - +import spydrnet as sdn class Composer: - def __init__(self, definition_list=None, write_blackbox=False): + def __init__(self, definition_list=None, write_blackbox=False, defparam = False, reverse=False): """ Write a verilog netlist from SDN netlist parameters ---------- - definition_list - (list[str]) list of defintions to write + definition_list - (list[str]) list of definitions to write write_blackbox - (bool) Skips writing black boxes/verilog primitives + defparam - (bool) Compose parameters in *defparam* statements instead of using #() + reverse - (bool) Compose the netlist bottom to top """ self.file = None self.direction_string_map = dict() @@ -25,12 +27,15 @@ def __init__(self, definition_list=None, write_blackbox=False): self.indent_count = 4 # set the indentation level for various components self.write_blackbox = write_blackbox self.definition_list = definition_list + self.defparam = defparam + self.module_body_ports_written = [] + self.reverse = reverse def run(self, ir, file_out="out.v"): self._open_file(file_out) self._compose(ir) - + def _open_file(self, file_name): f = open(file_name, "w") self.file = f @@ -39,7 +44,10 @@ def _compose(self, netlist): self._write_header(netlist) instance = netlist.top_instance if instance is not None: - self._write_from_top(instance) + if self.reverse: + self._write_from_bottom(instance) + else: + self._write_from_top(instance) for library in netlist.libraries: for definition in library.definitions: if definition not in self.written: @@ -47,7 +55,7 @@ def _compose(self, netlist): def _write_header(self, netlist): self.file.write("//Generated from netlist by SpyDrNet\n") - self.file.write("//netlist name: " + netlist.name + "\n") + self.file.write("//netlist name: " + self._fix_name(netlist.name) + "\n") def _write_from_top(self, instance): #self.written = set() @@ -65,6 +73,28 @@ def _write_from_top(self, instance): "definition has no name set", definition) self._write_module(definition) + def _write_from_bottom(self, instance): + # ****this may need more work**** + visited = [] + to_write_list = deque() + to_write_stack = [] + to_write_list.append(instance.reference) + to_write_stack.append(instance.reference) + while(len(to_write_list) != 0): + definition = to_write_list.popleft() + if definition in visited: + continue + visited.append(definition) + for c in definition.children: + if c.reference not in visited: + to_write_list.append(c.reference) + to_write_stack.append(c.reference) + # print(list(x.name for x in to_write_stack if len(x.children) > 0)) + while(to_write_stack): + definition = to_write_stack.pop() + if definition not in self.written: + self.written.add(definition) + self._write_module(definition) ########################################################################### # Write verilog constructs @@ -110,7 +140,7 @@ def _write_module(self, definition): return if definition.library.name == "SDN_VERILOG_ASSIGNMENT": return # don't write assignment definitions - if definition.library.name == "SDN.verilog_primitives": + if definition.library.name == "hdi_primitives": if not self.write_blackbox: return self.file.write(vt.CELL_DEFINE) @@ -119,7 +149,7 @@ def _write_module(self, definition): self._write_module_header(definition) self._write_module_body(definition) self.file.write(vt.END_MODULE) - if definition.library.name == "SDN.verilog_primitives": + if definition.library.name == "hdi_primitives": self.file.write(vt.NEW_LINE) self.file.write(vt.END_CELL_DEFINE) @@ -142,7 +172,7 @@ def _write_module_header(self, definition): def _write_module_body(self, definition): '''write out the module body start with ports, then do assignments then instances''' self._write_module_body_ports(definition) - if definition.library.name != "SDN.verilog_primitives": + if definition.library.name != "hdi_primitives": self._write_module_body_cables(definition) self._write_assignments(definition) self._write_module_body_instances(definition) @@ -158,15 +188,24 @@ def _write_module_body_instance(self, instance): self.file.write(self.indent_count * vt.SPACE) self._write_name(instance.reference) self.file.write(vt.SPACE) - if "VERILOG.Parameters" in instance: - self._write_instance_parameters(instance) - self.file.write(self.indent_count * vt.SPACE) + if not self.defparam: + if "VERILOG.Parameters" in instance: + self._write_instance_parameters(instance) + self.file.write(self.indent_count * vt.SPACE) self._write_name(instance) self.file.write(vt.NEW_LINE) self._write_instance_ports(instance) self.file.write(vt.NEW_LINE) + if self.defparam: + if "VERILOG.Parameters" in instance: + for key, value in instance["VERILOG.Parameters"].items(): + to_write = (self.indent_count * vt.SPACE) + vt.DEFPARAM + vt.SPACE + to_write += self._fix_name(instance.name) + vt.DOT + key + vt.EQUAL + to_write += value + vt.SEMI_COLON + vt.NEW_LINE + self.file.write(to_write) def _write_module_body_ports(self, definition): + self.module_body_ports_written = [] for p in definition.ports: self._write_module_body_port(p) self.file.write(vt.NEW_LINE) @@ -176,6 +215,9 @@ def _write_module_body_port(self, port): if len(cables) == 0: cables.append(port) #adding the port will let composer to still print out disconnected ports for c in cables: + if c.name in self.module_body_ports_written: + continue + self.module_body_ports_written.append(c.name) self._write_star_constraints(port) self.file.write(self.indent_count * vt.SPACE) self.file.write(self.direction_string_map[port.direction]) @@ -186,7 +228,9 @@ def _write_module_body_port(self, port): self.file.write(vt.NEW_LINE) def _write_module_body_cables(self, definition): - for c in definition.cables: + cable_list = list(c for c in definition.cables) + cable_list.reverse() + for c in cable_list: self._write_module_body_cable(c) self.file.write(vt.NEW_LINE) @@ -208,17 +252,47 @@ def _write_concatenation(self, wires): {wire_1, wire_2}''' self.file.write(vt.OPEN_BRACE) first = True + previous_cable = Cable() + first_index = 0 + previous_index = 0 + has_to_write = False for w in wires: if w is not None: - if not first: - self.file.write(vt.COMMA) - self.file.write(vt.SPACE) index = self._index_of_wire_in_cable(w) - self._write_bundle_with_indicies(w.cable, index, None) + if w.cable.name == previous_cable.name: + if index == (previous_index - 1): + previous_index = index + else: + #write the previous and save new stuff + if not first: + self.file.write(vt.COMMA) + self.file.write(vt.SPACE) + self._write_bundle_with_indicies(previous_cable, previous_index, first_index) + first = False + previous_cable = w.cable + first_index = index + previous_index = index + else: + if has_to_write: + #write the previous and save new stuff + if not first: + self.file.write(vt.COMMA) + self.file.write(vt.SPACE) + self._write_bundle_with_indicies(previous_cable, previous_index, first_index) + first = False + previous_cable = w.cable + first_index = index + previous_index = index + has_to_write = True else: - break + None + # break + if has_to_write: + if not first: + self.file.write(vt.COMMA) + self.file.write(vt.SPACE) + self._write_bundle_with_indicies(previous_cable, previous_index, first_index) - first = False self.file.write(vt.CLOSE_BRACE) def _write_module_header_ports(self, definition): @@ -234,6 +308,11 @@ def _write_module_header_ports(self, definition): self.file.write(vt.CLOSE_PARENTHESIS) self.file.write(vt.SEMI_COLON) self.file.write(vt.NEW_LINE) + + def pin_sort_func(self, p): + if isinstance(p, sdn.OuterPin): + return p.inner_pin.port.pins.index(p.inner_pin) + return p.port.pins.index(p) def _write_module_header_port(self, port): '''does not write the input output or width, @@ -242,8 +321,10 @@ def _write_module_header_port(self, port): aliased = self._is_pinset_concatenated(port.pins, port.name) if aliased: wires = [] - for p in port.pins: - wires.append(p.wire) + pin_list = list(p for p in port.pins) + pin_list.sort(reverse=True, key=self.pin_sort_func) + for pin in pin_list: + wires.append(pin.wire) self.file.write(vt.DOT) self._write_name(port) self.file.write(vt.OPEN_PARENTHESIS) @@ -358,13 +439,51 @@ def _write_instance_ports(self, instance): if not first: self.file.write(vt.COMMA) self.file.write(vt.NEW_LINE) - self._write_instance_port(instance, p) + if p.name: + self._write_instance_port(instance, p) + else: + self._write_implicitly_mapped_instance_port(instance, p) first = False self.file.write(vt.NEW_LINE) self.file.write(self.indent_count * vt.SPACE) self.file.write(vt.CLOSE_PARENTHESIS) self.file.write(vt.SEMI_COLON) + def _write_implicitly_mapped_instance_port(self, instance, port): + '''Ports that have no name must be implicitly mapped. E.g. inst(VCC_net) rather than inst(.p(VCC_net))''' + self.file.write(2*self.indent_count * vt.SPACE) + # self.file.write(vt.DOT) + # self._write_name(port) + # self.file.write(vt.OPEN_PARENTHESIS) + pins = [] + for p in port.pins: + pins.append(instance.pins[p]) + if pins[0].wire is not None: + name = pins[0].wire.cable.name + else: + name = None + concatenated = self._is_pinset_concatenated(pins, name) + wires = [] + pin_list = list(p for p in port.pins) + pin_list.sort(reverse=True, key=self.pin_sort_func) + for pin in pin_list: + wires.append(pin.wire) + if concatenated: + self._write_concatenation(wires) + else: + if pins[0].wire is not None: + last = -1 + wl = wires[last] + wr = wires[0] + while wl is None: # get the last named non none wire. + last = last - 1 + wl = wires[last] + il = self._index_of_wire_in_cable(wl) + ir = self._index_of_wire_in_cable(wr) + self._write_bundle_with_indicies(wl.cable, ir, il) + + # self.file.write(vt.CLOSE_PARENTHESIS) + def _write_instance_port(self, instance, port): self.file.write(2*self.indent_count * vt.SPACE) self.file.write(vt.DOT) @@ -379,10 +498,15 @@ def _write_instance_port(self, instance, port): name = None concatenated = self._is_pinset_concatenated(pins, name) wires = [] + sorted_wires = [] + pin_list = list(p for p in pins) + pin_list.sort(reverse=True, key=self.pin_sort_func) + for pin in pin_list: + sorted_wires.append(pin.wire) for p in pins: wires.append(p.wire) if concatenated: - self._write_concatenation(wires) + self._write_concatenation(sorted_wires) else: if pins[0].wire is not None: last = -1 @@ -400,11 +524,17 @@ def _write_instance_port(self, instance, port): def _write_name(self, o): '''write the name of an o. this is split out to give an error message if the name is not set In the future this could be changed to add a name to os that do not have a name set''' - assert o.name is not None, self._error_string("name of o is not set", o) - if o.name[0] == '\\': - assert o.name[-1] == ' ', self._error_string( - "the o name starts with escape and does not end with a space.", o) - self.file.write(o.name) + name = o.name + assert name is not None, self._error_string("name of o is not set", o) + name = self._fix_name(name) + self.file.write(name) + + def _fix_name(self, name): + if name[0] == '\\': + if name[-1] != " ": + name+=" " + return name + def _write_brackets_defining(self, bundle): '''write the brackets for port or cable definitions''' @@ -455,6 +585,7 @@ def _write_brackets(self, bundle, low_index, high_index): self._error_string("attempted to index bundle out of bounds at " + str(high_index), bundle) return elif (low_index == lower_bundle and high_index == upper_bundle) or (low_index == None and high_index == None): + self.file.write("[" + str(high_index) + ":" + str(low_index) + "]") return elif low_index == high_index or low_index is None or high_index is None: index = low_index @@ -513,7 +644,20 @@ def _is_pinset_concatenated(self, pins, name): if next_name != name and not now_none: aliased = True break - + + # if all the wires connected to the pins are from the same cable, don't count as aliased + # otherwise, return previous answer + # TODO maybe also check if all of the cable is used. So no skipped wires. + # name = None + # for p in pins: + # if p.wire is None: + # continue + # if not name: + # name = p.wire.cable.name + # continue + # if p.wire.cable.name is not name: + # return aliased + # return False return aliased def _all_wires_and_cables_from_pinset(self, pins): diff --git a/spydrnet/composers/verilog/tests/test_composer.py b/spydrnet/composers/verilog/tests/test_composer.py index e18528af..1d693899 100644 --- a/spydrnet/composers/verilog/tests/test_composer.py +++ b/spydrnet/composers/verilog/tests/test_composer.py @@ -5,12 +5,13 @@ import os import tempfile import glob +from pathlib import Path class TestVerilogComposer(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - cls.dir_of_verilog_netlists = os.path.join(sdn.base_dir, "support_files", "verilog_netlists") + cls.dir_of_verilog_netlists = Path(sdn.base_dir, "support_files", "verilog_netlists") cls.verilog_files = sorted(glob.glob(os.path.join(cls.dir_of_verilog_netlists, "*.v.zip")), key = os.path.getsize) @unittest.skip("Test takes a long time right now.") @@ -19,7 +20,7 @@ def test_large_verilog_compose(self): errors = 0 for ii, filename in enumerate(self.verilog_files): with self.subTest(i=ii): - if os.path.getsize(filename) <= 1024 * 10: + if Path(filename).stat().st_size <= 1024 * 10: continue if filename.endswith(".zip"): with tempfile.TemporaryDirectory() as tempdirectory: @@ -28,7 +29,7 @@ def test_large_verilog_compose(self): # vp = sdn.parsers.verilog.parser.VerilogParser.from_filename(os.path.join(directory, filename)) # netlist = vp.parse() netlist = parsers.parse(filename) - composers.compose(netlist, os.path.join(tempdirectory, os.path.basename(filename) + "-spydrnet.v")) + composers.compose(netlist, Path(tempdirectory, Path(filename).name + "-spydrnet.v")) #comp.run(netlist,"temp2/"+filename[:len(filename)-6] + "-spydrnet.v") # comp.run(netlist,os.path.join(tempdirectory, filename[:len(filename)-6] + "-spydrnet.v")) i+=1 @@ -49,7 +50,7 @@ def test_small_verilog_compose(self): errors = 0 for ii, filename in enumerate(self.verilog_files): with self.subTest(i=ii): - if os.path.getsize(filename) > 1024 * 10: + if Path(filename).stat().st_size > 1024 * 10: continue if filename.endswith(".zip"): with tempfile.TemporaryDirectory() as tempdirectory: @@ -58,7 +59,7 @@ def test_small_verilog_compose(self): # vp = sdn.parsers.verilog.parser.VerilogParser.from_filename(os.path.join(directory, filename)) # netlist = vp.parse() netlist = parsers.parse(filename) - composers.compose(netlist, os.path.join(tempdirectory, os.path.basename(filename) + "-spydrnet.v")) + composers.compose(netlist, Path(tempdirectory, Path(filename).name + "-spydrnet.v")) #comp.run(netlist,"temp2/"+filename[:len(filename)-6] + "-spydrnet.v") # comp.run(netlist,os.path.join(tempdirectory, filename[:len(filename)-6] + "-spydrnet.v")) i+=1 @@ -79,8 +80,8 @@ def test_definition_list_option(self): self.dir_of_verilog_netlists, "*4bitadder.v.zip")): with tempfile.TemporaryDirectory() as tempdirectory: netlist = parsers.parse(filename) - out_file = os.path.join( - tempdirectory, os.path.basename(filename) + "-spydrnet.v") + out_file = Path( + tempdirectory, Path(filename).name + "-spydrnet.v") composers.compose(netlist, out_file, definition_list=['adder']) with open(out_file, "r") as fp: @@ -98,8 +99,8 @@ def test_write_blackbox_option(self): self.dir_of_verilog_netlists, "*4bitadder.v.zip")): with tempfile.TemporaryDirectory() as tempdirectory: netlist = parsers.parse(filename) - out_file = os.path.join( - tempdirectory, os.path.basename(filename) + "-spydrnet.v") + out_file = Path( + tempdirectory, Path(filename).name + "-spydrnet.v") composers.compose(netlist, out_file, write_blackbox=False) with open(out_file, "r") as fp: diff --git a/spydrnet/composers/verilog/tests/test_composer_unit.py b/spydrnet/composers/verilog/tests/test_composer_unit.py index db9727be..2c808207 100644 --- a/spydrnet/composers/verilog/tests/test_composer_unit.py +++ b/spydrnet/composers/verilog/tests/test_composer_unit.py @@ -138,13 +138,15 @@ def initialize_instance_port_connections(self, instance, definition): single_bit_expected = "." + single_bit_port.name + "(" + single_bit_cable.name + ")" - multi_bit_expected = "." + multi_bit_port.name + "(" + multi_bit_cable.name + ")" + multi_bit_expected = "." + multi_bit_port.name + "(" + multi_bit_cable.name + "[" + str(len(multi_bit_cable.wires) - 1 + multi_bit_cable.lower_index) + ":" + \ + str(multi_bit_cable.lower_index) + "]"")" - offset_expected = "." + multi_bit_port_offset.name + "(" + multi_bit_cable.name + ")" + offset_expected = "." + multi_bit_port_offset.name + "(" + multi_bit_cable.name + "[" + str(len(multi_bit_cable.wires) - 1 + multi_bit_cable.lower_index) + ":" + \ + str(multi_bit_cable.lower_index) + "]"")" partial_expected = "." + partial_port.name + "(" + multi_bit_cable.name + "[1:0])" - concatenated_expected = "." + concatenated_port.name + "({" + ccs[0].name + ', ' + ccs[1].name + ', ' + ccs[2].name + ', ' + ccs[3].name + "})" + concatenated_expected = "." + concatenated_port.name + "({" + ccs[3].name + ', ' + ccs[2].name + ', ' + ccs[1].name + ', ' + ccs[0].name + "})" return single_bit_port, single_bit_expected, \ multi_bit_port, multi_bit_expected, \ @@ -278,7 +280,7 @@ def test_write_brackets_multi_bit(self): assert composer.file.compare("[2]") composer.file.clear() composer._write_brackets(port, 0, 3) - assert composer.file.compare("") + assert composer.file.compare("[3:0]") composer.file.clear() composer._write_brackets(port, 1, 2) assert composer.file.compare("[2:1]") @@ -297,7 +299,7 @@ def test_write_brackets_multi_bit(self): assert composer.file.compare("[2]") composer.file.clear() composer._write_brackets(cable, 0, 3) - assert composer.file.compare("") + assert composer.file.compare("[3:0]") composer.file.clear() composer._write_brackets(cable, 1, 2) assert composer.file.compare("[2:1]") @@ -336,7 +338,7 @@ def test_write_brackets_multi_bit_offset(self): assert composer.file.compare("[6]") composer.file.clear() composer._write_brackets(port, 4, 7) - assert composer.file.compare("") + assert composer.file.compare("[7:4]") composer.file.clear() composer._write_brackets(port, 5, 6) assert composer.file.compare("[6:5]") @@ -355,7 +357,7 @@ def test_write_brackets_multi_bit_offset(self): assert composer.file.compare("[6]") composer.file.clear() composer._write_brackets(cable, 4, 7) - assert composer.file.compare("") + assert composer.file.compare("[7:4]") composer.file.clear() composer._write_brackets(cable, 5, 6) assert composer.file.compare("[6:5]") @@ -414,7 +416,6 @@ def test_write_none_name(self): o = sdn.Cable() composer._write_name(o) - @unittest.expectedFailure def test_write_invalid_name(self): composer = self.initialize_tests() o = sdn.Cable() @@ -568,7 +569,7 @@ def test_write_module_ports_header_and_body_alias(self): c2.wires[0].connect_pin(port_alias.pins[1]) composer._write_module_header_port(port_alias) - assert composer.file.compare("." + port_alias.name + "({"+ c1.name + ", " + c2.name +"})") + assert composer.file.compare("." + port_alias.name + "({"+ c2.name + ", " + c1.name +"})") composer.file.clear() composer._write_module_body_port(port_alias) assert composer.file.compare("input " + c1.name + ";\n " + "input " + c2.name + ";\n") diff --git a/spydrnet/ir/instance.py b/spydrnet/ir/instance.py index 50f0255b..e3b36ac9 100644 --- a/spydrnet/ir/instance.py +++ b/spydrnet/ir/instance.py @@ -219,6 +219,9 @@ def __str__(self): rep += '>' return rep + def __lt__(self, other): + return self.name < other.name + @property def is_top_instance(self): return self._is_top_instance diff --git a/spydrnet/ir/outerpin.py b/spydrnet/ir/outerpin.py index fd540434..d993b66a 100644 --- a/spydrnet/ir/outerpin.py +++ b/spydrnet/ir/outerpin.py @@ -93,3 +93,6 @@ def clone(self): c = self._clone(dict()) c._clone_rip() return c + + def index(self): + return self._inner_pin.port.pins.index(self._inner_pin) \ No newline at end of file diff --git a/spydrnet/ir/tests/__init__.py b/spydrnet/ir/tests/__init__.py index be6a8671..101786e3 100644 --- a/spydrnet/ir/tests/__init__.py +++ b/spydrnet/ir/tests/__init__.py @@ -1,3 +1,4 @@ -import os +from pathlib import Path + +data_dir = Path(Path(Path(__file__).parent, "data")).absolute() -data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "data")) diff --git a/spydrnet/parsers/__init__.py b/spydrnet/parsers/__init__.py index 05061a96..b27fe5b9 100644 --- a/spydrnet/parsers/__init__.py +++ b/spydrnet/parsers/__init__.py @@ -1,6 +1,7 @@ -import os import zipfile import tempfile +from pathlib import Path +import os """ Init for Spydrnet. The functions below can be called directly @@ -35,9 +36,9 @@ def parse(filename, **kwArgs): The same applies for EBLIF files """ - basename_less_final_extension = os.path.splitext( - os.path.basename(filename))[0] - extension = get_lowercase_extension(filename) + + basename_less_final_extension = Path(filename).stem + extension = get_lowercase_extension(filename) if extension == ".zip": assert zipfile.is_zipfile(filename), \ "Input filename {} with extension .zip is not a zip file.".format( @@ -67,11 +68,27 @@ def _parse(filename, **kwArgs): parser = EBLIFParser.from_filename(filename) else: raise RuntimeError("Extension {} not recognized.".format(extension)) - parser.parse() + parser.parse() + + if 'architecture' in kwArgs: + read_primitive_library(kwArgs["architecture"], parser.netlist) + if extension in [".eblif",".blif"]: + set_eblif_names(parser.netlist) return parser.netlist def get_lowercase_extension(filename): - extension = os.path.splitext(filename)[1] + extension = Path(filename).suffix extension_lower = extension.lower() return extension_lower + +def read_primitive_library(architecture, netlist): + from spydrnet.parsers.primitive_library_reader import PrimitiveLibraryReader + reader = PrimitiveLibraryReader(architecture, netlist) + reader.run() + +def set_eblif_names(netlist): + from spydrnet.parsers.eblif.eblif_parser import EBLIFParser + eblif_parser = EBLIFParser() + eblif_parser.netlist = netlist + eblif_parser.set_subcircuit_names_by_convention() \ No newline at end of file diff --git a/spydrnet/parsers/eblif/eblif_parser.py b/spydrnet/parsers/eblif/eblif_parser.py index ace56da9..1db1c7c4 100644 --- a/spydrnet/parsers/eblif/eblif_parser.py +++ b/spydrnet/parsers/eblif/eblif_parser.py @@ -1,12 +1,10 @@ import spydrnet as sdn -from spydrnet.parsers.eblif.eblif_tokens import * +import spydrnet.parsers.eblif.eblif_tokens as et from spydrnet.parsers.eblif.eblif_tokenizer import Tokenizer from spydrnet.ir import Netlist -from spydrnet.ir import Definition from spydrnet.ir import Instance from spydrnet.ir import Port from spydrnet.ir import InnerPin -from spydrnet.ir.outerpin import OuterPin from spydrnet.ir import Wire from spydrnet.util.selection import Selection @@ -14,9 +12,38 @@ class EBLIFParser: """ Parse BLIF and EBLIF files into SpyDrNet - The first model is considered the netlist's top instance. Any model that follows is used as information for the definitions (aka primitive information) - """ + + class BlackboxHolder: + '''this is an internal class that helps manage + modules that are instanced before they are declared''' + + def __init__(self): + self.name_lookup = {} + self.defined = set() + + def get_blackbox(self, name): + '''creates or returns the black box based on the name''' + if name in self.name_lookup: + return self.name_lookup[name] + else: + definition = sdn.Definition() + definition.name = name + self.name_lookup[name] = definition + return definition + + def define(self, name): + '''adds the name to the defined set''' + self.defined.add(self.name_lookup[name]) + + def get_undefined_blackboxes(self): + '''return an iterable of all undefined blackboxes''' + undef = set() + for v in self.name_lookup.values(): + if v not in self.defined: + undef.add(v) + return undef + ####################################################### # setup functions ####################################################### @@ -36,19 +63,14 @@ def __init__(self): self.file_name = None self.tokenizer = None self.netlist = None - self.current_instance_info = dict() - # self.top_instance = None + self.current_instance_info = {} self.current_instance = None - # self.latest_definition = None - # self.information = dict() - self.definitions = dict() - self.cables = dict() - self.default_names = dict() - self.top_level_input_ports = dict() - self.top_level_output_ports = dict() - self.comments = list() + self.default_names = {} + self.comments = [] self.current_model = None - # self.last_net = None + self.primitives = None + self.work = None + self.blackbox_holder = self.BlackboxHolder() def createTokenizer(self): self.tokenizer = Tokenizer(self.file_name) @@ -60,284 +82,240 @@ def parse(self): self.createTokenizer() self.parse_eblif() return self.netlist - + def parse_eblif(self): - while(self.tokenizer.has_next()): + netlist = Netlist() + self.netlist = netlist + self.work = self.netlist.create_library("work") + self.primitives = self.netlist.create_library("hdi_primitives") + while self.tokenizer.has_next(): token = self.tokenizer.next() - if token == COMMENT: + if token == et.COMMENT: self.parse_comment() - elif token == MODEL: - if self.netlist: - self.parse_other_model() - else: - self.parse_top_model() + elif token == et.MODEL: + self.parse_model() else: None self.set_subcircuit_names_by_convention() self.insert_comments_into_netlist_data() - - def parse_top_model(self): - netlist = Netlist() - name = self.tokenizer.next() - netlist.name = name - self.netlist = netlist - self.parse_top_instance() - self.parse_model_helper() - - def parse_other_model(self): - name = self.tokenizer.next() - try: - self.definitions[name] - except KeyError: - # print("Error, no definition found") - self.definitions[name] = self.create_new_definition() - definition = self.definitions[name] - self.tokenizer.next() - - while(self.tokenizer.peek() == INPUTS): - self.parse_other_model_input_ports(definition) - - while(self.tokenizer.peek() == OUTPUTS): - self.parse_other_model_output_ports(definition) - - while (True): - self.tokenizer.next() - if (self.tokenizer.token == BLACKBOX): - definition["EBLIF.blackbox"] = True - elif self.tokenizer.token == END: - break - - def parse_other_model_input_ports(self,definition): - self.expect(INPUTS) - self.tokenizer.next() - while (self.tokenizer.token is not NEW_LINE): - port_name, index = self.get_port_name_and_index(self.tokenizer.token) - index = int(index) - port = next(definition.get_ports(filter=lambda x: x.name == port_name)) - port.direction = sdn.IN - self.tokenizer.next() - - def parse_other_model_output_ports(self,definition): - self.expect(OUTPUTS) - self.tokenizer.next() - while (self.tokenizer.token is not NEW_LINE): - port_name, index = self.get_port_name_and_index(self.tokenizer.token) - port = next(definition.get_ports(filter=lambda x: x.name == port_name)) - port.direction = sdn.OUT - self.tokenizer.next() - - def parse_model_helper(self): - while(self.tokenizer.has_next()): + self.add_blackbox_definitions() + + def parse_model(self): + model_name = self.tokenizer.next() + self.default_names = {} + self.parse_model_header(model_name) + self.parse_model_helper(model_name) + + def parse_model_helper(self, model_name): + model = self.blackbox_holder.get_blackbox(model_name) + is_blackbox = False + while self.tokenizer.has_next(): token = self.tokenizer.next() - if token == COMMENT: + if token == et.COMMENT: self.parse_comment() - elif token == SUBCIRCUIT: - self.parse_sub_circuit() - elif token == GATE: - self.parse_sub_circuit(is_gate=True) - elif token == LATCH: + elif token == et.SUBCIRCUIT: + self.parse_subcircuit() + elif token == et.GATE: + self.parse_subcircuit(is_gate=True) + elif token == et.LATCH: self.parse_latch() - elif token == NAMES: + elif token == et.NAMES: self.parse_name() - elif token == CONN: + elif token == et.CONN: self.parse_conn() - elif token == END: + elif token == et.BLACKBOX: + self.make_blackbox() + is_blackbox = True + elif token == et.END: break else: None - - def parse_top_instance(self): - # Libraries aren't in blif, so just create a single library - library = self.netlist.create_library(name="library_1") - top_instance_def = library.create_definition(name=self.netlist.name) - top_instance = Instance(name=top_instance_def.name) - top_instance.reference = top_instance_def - self.netlist.set_top_instance(top_instance) - self.current_model = top_instance + if is_blackbox: + self.primitives.add_definition(model) + else: + self.work.add_definition(model) + + def parse_model_header(self, model_name): + model_def = self.blackbox_holder.get_blackbox(model_name) + self.blackbox_holder.define(model_name) + if not self.netlist.top_instance: + parent_instance = Instance(name=model_def.name) + parent_instance.reference = model_def + self.netlist.set_top_instance(parent_instance) + self.netlist.name = model_def.name + self.current_model = model_def self.tokenizer.next() # should be end of line so proceed to next line - self.parse_top_level_ports() - - def parse_top_level_ports(self): - while(self.tokenizer.peek() == INPUTS): - self.parse_top_level_inputs() - while(self.tokenizer.peek() == OUTPUTS): - self.parse_top_level_outputs() - - while(self.tokenizer.peek() == CLOCK): - self.parse_top_level_clock() - - def parse_top_level_inputs(self): - self.expect(INPUTS) + self.parse_model_ports() + + def parse_model_ports(self): + while self.tokenizer.peek() == et.INPUTS: + self.parse_input_ports() + while self.tokenizer.peek() == et.OUTPUTS: + self.parse_output_ports() + while self.tokenizer.peek() == et.CLOCK: + self.parse_clock() + + def parse_input_ports(self): + self.expect(et.INPUTS) self.tokenizer.next() - while (self.tokenizer.token is not NEW_LINE): + while self.tokenizer.token is not et.NEW_LINE: port_name, index = self.get_port_name_and_index(self.tokenizer.token) index = int(index) - port = None - pin = None - if (port_name in self.top_level_input_ports.keys()): - port= self.top_level_input_ports[port_name] - pin = port.create_pin() + port = next(self.current_model.get_ports(port_name), None) + if not port: + port = self.create_model_port(sdn.Port.Direction.IN, port_name) + self.current_model.add_port(port) else: - port = self.create_top_level_port(sdn.Port.Direction.IN,port_name) - self.netlist.top_instance.reference.add_port(port) - self.top_level_input_ports[port_name] = port - pin = port.create_pin() - self.connect_pins_to_wires(pin,port_name,index) + port.direction=sdn.IN + while len(port.pins) < index+1: + port.create_pin() + pin = port.pins[index] + self.connect_pin_to_wire(pin,port_name,index) self.tokenizer.next() - - def parse_top_level_outputs(self): - self.expect(OUTPUTS) + + def parse_output_ports(self): + self.expect(et.OUTPUTS) self.tokenizer.next() - while (self.tokenizer.token is not NEW_LINE): + while self.tokenizer.token is not et.NEW_LINE: + is_inout = False port_name, index = self.get_port_name_and_index(self.tokenizer.token) index = int(index) - port = None - pin = None - if (port_name in self.top_level_output_ports.keys()): - port= self.top_level_output_ports[port_name] - pin = port.create_pin() + port = next(self.current_model.get_ports(port_name), None) + if not port: + port = self.create_model_port(sdn.Port.Direction.OUT, port_name) + self.current_model.add_port(port) + if port.direction in {sdn.IN, sdn.INOUT}: # it's an input port and now an output, so it's inout + port.direction = sdn.INOUT + is_inout = True else: - port = self.create_top_level_port(sdn.Port.Direction.OUT,port_name) - self.netlist.top_instance.reference.add_port(port) - self.top_level_output_ports[port_name] = port - pin = port.create_pin() - self.connect_pins_to_wires(pin,port_name,index) + port.direction = sdn.OUT + while len(port.pins) < index+1: + port.create_pin() + pin = port.pins[index] + if not is_inout: + self.connect_pin_to_wire(pin,port_name,index) self.tokenizer.next() - - def parse_top_level_clock(self): - self.expect(CLOCK) + + def create_model_port(self, port_direction, port_name): + port = Port(direction=port_direction) + port.name = port_name + return port + + def parse_clock(self): + self.expect(et.CLOCK) self.tokenizer.next() try: - self.netlist.top_instance["EBLIF.clock"] + self.current_model["EBLIF.clock"] except KeyError: - self.netlist.top_instance["EBLIF.clock"] = list() - while (self.tokenizer.token is not NEW_LINE): - self.netlist.top_instance["EBLIF.clock"].append(self.tokenizer.token) + self.current_model["EBLIF.clock"] = [] + while self.tokenizer.token is not et.NEW_LINE: + self.current_model["EBLIF.clock"].append(self.tokenizer.token) self.tokenizer.next() - def create_top_level_port(self,port_direction,port_name): - port = Port(direction=port_direction) - port.name = port_name - return port - - def connect_pins_to_wires(self, pin, cable_name, wire_index): + def connect_pin_to_wire(self, pin, cable_name, wire_index): # connect the port to a wire in a cable named after it - cable = None - if (cable_name in self.cables.keys()): - cable = self.cables[cable_name] - else: - cable = self.netlist.top_instance.reference.create_cable(name=cable_name) - self.cables[cable_name] = cable - try: - cable.wires[wire_index] - except IndexError: - while (len(cable.wires)-1 < wire_index): # add wires to cable until we get the right index - wire = Wire() - cable.add_wire(wire,wire_index) - # wire = Wire() - # cable.add_wire(wire,wire_index) + cable = next(self.current_model.get_cables(cable_name), None) + if not cable: + cable = self.current_model.create_cable(name=cable_name) + while (len(cable.wires)-1) < wire_index: # add wires to cable until we get the right index + wire = Wire() + cable.add_wire(wire,wire_index) wire = cable.wires[wire_index] wire.connect_pin(pin) - def parse_sub_circuit(self,is_gate=False): - # print(self.tokenizer.token) + def parse_subcircuit(self, is_gate=False): self.current_instance_info.clear() reference_model = self.tokenizer.next() - definition = None - if reference_model not in list(key for key in self.definitions.keys()): # if the reference model is not found in the netlist, create it - definition = self.create_new_definition() - # Note: if a definition is created, instance information is automatically collected - else: - definition = self.definitions[reference_model] - self.collect_subcircuit_information() - instance = self.netlist.top_instance.reference.create_child(reference = definition) + self.check_hierarchy(reference_model) + definition = self.blackbox_holder.get_blackbox(reference_model) + self.tokenizer.next() + while self.tokenizer.token is not et.NEW_LINE: + self.parse_subcircuit_port(definition) + self.tokenizer.next() + + instance = self.current_model.create_child(reference=definition) self.current_instance = instance if is_gate: instance["EBLIF.type"] = "EBLIF.gate" else: instance["EBLIF.type"] = "EBLIF.subckt" + self.assign_instance_a_default_name(instance) - # print(self.current_instance_info) self.connect_instance_pins(instance) - self.check_for_and_add_more_instance_info() + self.parse_instance_info() - def create_new_definition(self): - definition = self.netlist.libraries[0].create_definition(name = self.tokenizer.token) - # print("GONNA MAKE DEFINITION: " + self.tokenizer.token) - self.tokenizer.next() - # print("now token is "+self.tokenizer.token) - while (self.tokenizer.token is not NEW_LINE): - self.parse_definition_port(definition) - self.tokenizer.next() - self.definitions[definition.name] = definition - return definition - - def parse_definition_port(self,definition): - port = Port() + def parse_subcircuit_port(self, definition): token = self.tokenizer.token equal_index = token.find("=") port_name_and_index = token[:equal_index] - name,index = self.get_port_name_and_index(port_name_and_index) - if name in list(x.name for x in definition.ports): - # print(name + " is in "+ str(list(x.name for x in definition.ports))) - port = next(x for x in definition.ports if x.name == name) + name, index = self.get_port_name_and_index(port_name_and_index) + port = next(definition.get_ports(name), None) + if not port: + port = definition.create_port(name=name) + if index > (len(port.pins) - 1): pin = InnerPin() port.add_pin(pin,index) - # print("added a pin at index "+str(index)) - self.current_instance_info[port_name_and_index] = token[equal_index+1:] - else: - new_port = Port() - new_port.name = name - pin = InnerPin() - new_port.add_pin(pin,index) - self.current_instance_info[port_name_and_index] = token[equal_index+1:] - definition.add_port(new_port) - # port.create_pin() - # return port - - def collect_subcircuit_information(self): - self.tokenizer.next() - while (self.tokenizer.token is not NEW_LINE): - token = self.tokenizer.token - equal_index = token.find("=") - port_name =token[:equal_index] - self.current_instance_info[port_name] = token[equal_index+1:] - self.tokenizer.next() + self.current_instance_info[port_name_and_index] = token[equal_index+1:] + + def check_hierarchy(self, child_definition_name): + if child_definition_name == self.netlist.top_instance.reference.name: + # print(self.current_definition.name + " is instancing the current top instance (" + name+ " which is a "+ self.netlist.top_instance.reference.name+")") + old_top_instance = self.netlist.top_instance + new_level = self.current_model + # we know the current top is not right. So now we can move it up a level. + # But double check to make sure nothing is instancing the potential new top. + # Move up levels until we reach a new top + if len(self.current_model.references) > 0: + current_level = list(x for x in self.current_model.references)[0] + while True: + current_level = current_level.parent + try: + current_level.parent + except AttributeError: + new_level = current_level + break + + self.netlist.top_instance = sdn.Instance() + self.netlist.top_instance.name = new_level.name + self.netlist.top_instance.reference = new_level + self.netlist.name = new_level.name + + # this instance should just go away. It was created to be the top instance but we don't want that + # it has no parent. And now with no reference, it should have no ties to the netlist. + old_top_instance.reference = None def connect_instance_pins(self,instance): - for key in self.current_instance_info.keys(): - cable_info = self.current_instance_info[key] - cable_name,cable_index = self.get_port_name_and_index(cable_info) # get connected cable name and wire index + for key, cable_info in self.current_instance_info.items(): + # cable_info = self.current_instance_info[key] + cable_name, cable_index = self.get_port_name_and_index(cable_info) # get connected cable name and wire index port_name, pin_index = self.get_port_name_and_index(key) # get own port name and pin index - if (cable_name == UNCONN): # intentionally disconnected so put that into metadata + if cable_name == et.UNCONN: # intentionally disconnected so put that into metadata try: - instance[UNCONN] + instance[et.UNCONN] except KeyError: - instance[UNCONN] = list() - instance[UNCONN].append(port_name+"["+str(pin_index)+"]") + instance[et.UNCONN] = [] + instance[et.UNCONN].append(port_name+"["+str(pin_index)+"]") continue - # print(port_name) - # print(list(x.inner_pin.port.name for x in instance.get_pins(selection=Selection.OUTSIDE))) - pin = next(instance.get_pins(selection=Selection.OUTSIDE,filter=lambda x: x.inner_pin.port.name == port_name and x.inner_pin is x.inner_pin.port.pins[pin_index])) - self.connect_pins_to_wires(pin,cable_name,cable_index) + + port = next(instance.get_ports(port_name)) + while len(port.pins) < (pin_index+1): # multibit port that isn't yet multibit + port.create_pin() + pin = next(instance.get_pins(selection=Selection.OUTSIDE, filter=lambda x: x.inner_pin.port.name == port_name and x.inner_pin is x.inner_pin.port.pins[pin_index])) + self.connect_pin_to_wire(pin, cable_name, cable_index) def parse_comment(self): token = self.tokenizer.next() comment = "" - while (token is not NEW_LINE): + while token is not et.NEW_LINE: comment+=token+" " token = self.tokenizer.next() self.comments.append(comment) - + def insert_comments_into_netlist_data(self): self.netlist["EBLIF.comment"] = self.comments - def expect(self, token): - self.tokenizer.next() - self.tokenizer.expect(token) - def get_port_name_and_index(self,string): - index_specified = (string[len(string)-1] == "]") + index_specified = (string[-1] == "]") if index_specified: open_bracket = string.rfind("[") if open_bracket == -1: @@ -349,101 +327,99 @@ def get_port_name_and_index(self,string): if ":" in index: old_index = index index = index[:index.find(':')] - print("Index was: "+old_index+". To avoid an error, it was changed to "+index) + print("EBLIFParser: Index was: " + old_index + ". To avoid an error, it was changed to " + index) return name, int(index) else: return string, 0 - def assign_instance_a_default_name(self,instance): + def assign_instance_a_default_name(self, instance): name = instance.reference.name index = 0 if name in self.default_names.keys(): - index = self.default_names[name]+1 + index = self.default_names[name] + 1 self.default_names[name]+=1 else: index = 0 self.default_names[name] = 0 - name = name+"_instance_"+str(index) + name = name + "_instance_" + str(index) instance.name = name - - def check_for_and_add_more_instance_info(self): - while(True): + + def parse_instance_info(self): + while True: peeked_token = self.tokenizer.peek() - if (peeked_token == PARAM): + if peeked_token == et.PARAM: self.parse_param() - elif (peeked_token == CNAME): + elif peeked_token == et.CNAME: self.parse_cname() - elif (peeked_token == ATTRIBUTE): + elif peeked_token == et.ATTRIBUTE: self.parse_attribute() else: - if (peeked_token == NEW_LINE): + if peeked_token == et.NEW_LINE: self.tokenizer.next() continue break - + def parse_param(self): - self.expect(PARAM) + self.expect(et.PARAM) token = self.tokenizer.next() info = self.tokenizer.next() - try: + try: self.current_instance["EBLIF.param"] except KeyError: - self.current_instance["EBLIF.param"] = dict() + self.current_instance["EBLIF.param"] = {} self.current_instance["EBLIF.param"][token] = info - + def parse_cname(self): - self.expect(CNAME) + self.expect(et.CNAME) name = self.tokenizer.next() self.current_instance["EBLIF.cname"] = name self.current_instance.name = name def parse_attribute(self): - self.expect(ATTRIBUTE) + self.expect(et.ATTRIBUTE) key = self.tokenizer.next() value = self.tokenizer.next() try: self.current_instance["EBLIF.attr"] except KeyError: - self.current_instance["EBLIF.attr"] = dict() + self.current_instance["EBLIF.attr"] = {} self.current_instance["EBLIF.attr"][key] = value def parse_name(self): self.current_instance_info.clear() self.tokenizer.next() - # if self.look_for_true_false_undef(): - # return - port_nets = list() - while (self.tokenizer.token is not NEW_LINE): + port_nets = [] + while self.tokenizer.token is not et.NEW_LINE: port_nets.append(self.tokenizer.token) self.tokenizer.next() - single_output_covers = list() + single_output_covers = [] while (self.check_if_init_values(self.tokenizer.peek())): # make sure next token is init values single_output_cover=self.tokenizer.next() - single_output_cover+=" "+self.tokenizer.next() - self.tokenizer.next() + single_output_cover+=" " + possible_next = self.tokenizer.next() + if possible_next != et.NEW_LINE: + single_output_cover+=possible_next + self.tokenizer.next() single_output_covers.append(single_output_cover) - - # then make/get def called LUT_names_# where # is the # of ports-1 + + # then make/get def called logic-gate_# where # is the # of ports-1 name = "logic-gate_"+str(len(port_nets)-1) - try: - self.definitions[name] - except KeyError: - definition = self.netlist.libraries[0].create_definition(name=name) - self.definitions[name] = definition - for i in range(len(port_nets)-1): - port = self.create_names_port("in_"+str(i),Port.Direction.IN) + definition = self.blackbox_holder.get_blackbox(name) + for i in range(len(port_nets)-1): + port_name = "in_" + str(i) + port = next(definition.get_ports(port_name), None) + if not port: + port = self.create_names_port("in_" + str(i), Port.Direction.IN) definition.add_port(port) - definition.add_port(self.create_names_port("out",Port.Direction.OUT)) - definition = self.definitions[name] + if not next(definition.get_ports("out"), None): + definition.add_port(self.create_names_port("out", Port.Direction.OUT)) # then create an instance of it - instance = self.netlist.top_instance.reference.create_child() - instance.reference = definition - instance["EBLIF.output_covers"] = single_output_covers - # self.assign_instance_a_default_name(instance) + instance = self.current_model.create_child(reference=definition) self.current_instance = instance + instance["EBLIF.output_covers"] = single_output_covers instance["EBLIF.type"] = "EBLIF.names" - + for port, net in zip(definition.get_ports(),port_nets): self.current_instance_info[port.name] = net if "unconn" in port_nets[len(port_nets)-1]: @@ -452,7 +428,7 @@ def parse_name(self): instance.name = port_nets[len(port_nets)-1] # by convention, the name of the instance is the name of the driven net self.connect_instance_pins(instance) - self.check_for_and_add_more_instance_info() + self.parse_instance_info() def check_if_init_values(self,string): allowed = {'1','0','-'} @@ -461,22 +437,7 @@ def check_if_init_values(self,string): return False return True - def look_for_true_false_undef(self): - if self.tokenizer.token in [TRUE_WIRE,FALSE_WIRE,UNDEF_WIRE]: - try: - self.netlist["EBLIF.default_wires"] - except KeyError: - self.netlist["EBLIF.default_wires"] = list() - self.netlist["EBLIF.default_wires"].append(self.tokenizer.token) - if (self.tokenizer.token is TRUE_WIRE): - self.tokenizer.next() - self.tokenizer.next() - else: - self.tokenizer.next() - return True - return False - - def create_names_port(self,name,direction): + def create_names_port(self, name, direction): port = Port(direction=direction) port.name = name port.create_pin() @@ -486,33 +447,27 @@ def parse_latch(self): self.current_instance_info.clear() self.tokenizer.next() # first collect the information port_order = ["input","output","type","control","init-val"] - token_list = list() - port_info = dict() - while (self.tokenizer.token is not NEW_LINE): + token_list = [] + port_info = {} + while self.tokenizer.token is not et.NEW_LINE: token_list.append(self.tokenizer.token) self.tokenizer.next() for order, token in zip(port_order,token_list): port_info[order] = token name = "generic-latch" - try: - self.definitions[name] - except KeyError: - definition = self.netlist.libraries[0].create_definition(name=name) - self.definitions[name] = definition + definition = self.blackbox_holder.get_blackbox(name) + if len(definition.ports) == 0: for order in port_info.keys() : - if order != "output": - port = self.create_names_port(order,Port.Direction.IN) + if order == "output": + port = self.create_names_port(order, Port.Direction.OUT) definition.add_port(port) else: - port = self.create_names_port(order,Port.Direction.OUT) + port = self.create_names_port(order, Port.Direction.IN) definition.add_port(port) - definition = self.definitions[name] # create an instance of it - instance = self.netlist.top_instance.reference.create_child() - instance.reference = definition - # self.assign_instance_a_default_name(instance) + instance = self.current_model.create_child(reference=definition) instance.name = port_info["output"] # by convention, the latch name is the name of the net it drives self.current_instance = instance instance["EBLIF.type"] = "EBLIF.latch" @@ -521,50 +476,38 @@ def parse_latch(self): for port, net in port_info.items(): self.current_instance_info[port] = net self.connect_instance_pins(instance) - self.check_for_and_add_more_instance_info() + self.parse_instance_info() def parse_conn(self): cable_one_info = self.tokenizer.next() cable_two_info = self.tokenizer.next() - cable_one_name,cable_one_index = self.get_port_name_and_index(cable_one_info) - cable_two_name,cable_two_index = self.get_port_name_and_index(cable_two_info) - wire_one,wire_two = self.get_connected_wires(cable_one_name,cable_one_index,cable_two_name,cable_two_index) - self.merge_wires(wire_one,wire_two) - - def get_connected_wires(self,cable_one_name,index_one,cable_two_name,index_two): - cable_one = None - cable_two = None - if (cable_one_name in self.cables.keys()): - cable_one = self.cables[cable_one_name] - else: - cable_one = self.netlist.top_instance.reference.create_cable(name=cable_one_name) - self.cables[cable_one_name] = cable_one - try: - cable_one.wires[index_one] - except IndexError: - wire = Wire() - cable_one.add_wire(wire,index_one) + cable_one_name, cable_one_index = self.get_port_name_and_index(cable_one_info) + cable_two_name, cable_two_index = self.get_port_name_and_index(cable_two_info) + wire_one, wire_two = self.get_connected_wires(cable_one_name, cable_one_index, cable_two_name, cable_two_index) + self.merge_wires(wire_one, wire_two) + + def get_connected_wires(self, cable_one_name, index_one, cable_two_name, index_two): + cable_one = next(self.current_model.get_cables(cable_one_name), None) + if not cable_one: + cable_one = self.current_model.create_cable(name=cable_one_name) + while len(cable_one.wires) < (index_one + 1): + cable_one.create_wire() wire_one = cable_one.wires[index_one] - if (cable_two_name in self.cables.keys()): - cable_two = self.cables[cable_two_name] - else: - cable_two = self.netlist.top_instance.reference.create_cable(name=cable_two_name) - self.cables[cable_two_name] = cable_two - try: - cable_two.wires[index_two] - except IndexError: - wire = Wire() - cable_two.add_wire(wire,index_two) + cable_two = next(self.current_model.get_cables(cable_two_name), None) + if not cable_two: + cable_two = self.current_model.create_cable(name=cable_two_name) + while len(cable_two.wires) < (index_two + 1): + cable_two.create_wire() wire_two = cable_two.wires[index_two] - return wire_one,wire_two + return wire_one, wire_two - def merge_wires(self,wire_one,wire_two): + def merge_wires(self, wire_one, wire_two): # merge them into one new wire inside a new cable and throw both wires away - name_one = wire_one.cable.name+"_"+str(wire_one.index()) - name_two = wire_two.cable.name+"_"+str(wire_two.index()) - new_cable_name = name_one+"_"+name_two - new_cable = self.current_model.reference.create_cable(name=new_cable_name) + name_one = wire_one.cable.name + "_" + str(wire_one.index()) + name_two = wire_two.cable.name + "_" + str(wire_two.index()) + new_cable_name = name_one + "_" + name_two + new_cable = self.current_model.create_cable(name=new_cable_name) new_wire = new_cable.create_wire() wire_one_pins = wire_one.pins.copy() @@ -580,13 +523,33 @@ def merge_wires(self,wire_one,wire_two): wire_two.cable.remove_wire(wire_two) def set_subcircuit_names_by_convention(self): # by convention, the instance names are defined by the net they drive - for instance in self.netlist.get_instances(): - if instance["EBLIF.type"] in ["EBLIF.subckt","EBLIF.gate"]: - if "EBLIF.cname" not in instance.data: - pin = next(instance.get_pins(selection=Selection.OUTSIDE,filter=lambda x: x.inner_pin.port.direction is sdn.OUT),None) - if pin: - if pin.wire: - name = pin.wire.cable.name - if len(pin.wire.cable.wires) > 1: - name+="_"+str(pin.wire.cable.wires.index(pin.wire)) - instance.name = name \ No newline at end of file + for instance in self.netlist.get_instances(): + if instance["EBLIF.type"] not in ["EBLIF.subckt", "EBLIF.gate"]: + continue + if "EBLIF.cname" in instance.data: + continue + iterator = instance.get_pins(selection=Selection.OUTSIDE, filter=lambda x: x.inner_pin.port.direction is sdn.OUT) + while True: + pin = next(iterator, None) + if pin: + if pin.wire: + name = pin.wire.cable.name + if len(pin.wire.cable.wires) > 1: + name+="_"+str(pin.wire.cable.wires.index(pin.wire)) + instance.name = name + break + else: + break + + def expect(self, token): + self.tokenizer.next() + self.tokenizer.expect(token) + + def make_blackbox(self): + # self.current_model["EBLIF.blackbox"] = True + self.current_model.remove_cables_from(self.current_model.cables) + + def add_blackbox_definitions(self): + for d in self.blackbox_holder.get_undefined_blackboxes(): + # d["EBLIF.blackbox"] = True + self.primitives.add_definition(d) diff --git a/spydrnet/parsers/eblif/eblif_tokenizer.py b/spydrnet/parsers/eblif/eblif_tokenizer.py index 25cf6060..4da5729d 100644 --- a/spydrnet/parsers/eblif/eblif_tokenizer.py +++ b/spydrnet/parsers/eblif/eblif_tokenizer.py @@ -4,6 +4,7 @@ import io import os from spydrnet.parsers.eblif.eblif_tokens import BACKSLASH +from pathlib import Path class Tokenizer: @staticmethod @@ -31,13 +32,15 @@ def __init__(self,input_source): if isinstance(input_source, str): if zipfile.is_zipfile(input_source): zip = zipfile.ZipFile(input_source) - filename = os.path.basename(input_source) + filename = Path(input_source).name filename = filename[:filename.rindex(".")] stream = zip.open(filename) stream = io.TextIOWrapper(stream) self.input_stream = stream else: self.input_stream = open(input_source, 'r') + elif isinstance(input_source, Path): + self.input_stream = open(input_source,"r") else: if isinstance(input_source, io.TextIOBase) is False: self.input_stream = io.TextIOWrapper(input_source) diff --git a/spydrnet/parsers/eblif/tests/test_eblif_parser.py b/spydrnet/parsers/eblif/tests/test_eblif_parser.py index d016e3af..1f9f1373 100644 --- a/spydrnet/parsers/eblif/tests/test_eblif_parser.py +++ b/spydrnet/parsers/eblif/tests/test_eblif_parser.py @@ -1,8 +1,8 @@ import unittest -import os import spydrnet as sdn from spydrnet import base_dir from spydrnet.util.selection import Selection +from pathlib import Path """ Test the EBLIFParser by parsing in a simple netlist and making sure that: @@ -11,7 +11,7 @@ """ class TestEBLIFParser(unittest.TestCase): def setUp(self): - self.netlist = sdn.parse(os.path.join(base_dir, 'support_files', 'eblif_netlists', "toggle.eblif.zip")) + self.netlist = sdn.parse(Path(base_dir, 'support_files', 'eblif_netlists', "toggle.eblif.zip")) self.definition_list = ["INV","BUFG","FDRE","IBUF","OBUF","toggle", "logic-gate_0"] def test_name(self): @@ -50,7 +50,7 @@ def test_cables(self): for cable in self.netlist.get_cables(): self.assertTrue(self.netlist_is_own_netlist(cable)) count+=1 - self.assertEqual(count,10) + self.assertEqual(count, 11) def netlist_is_own_netlist(self,object): netlist_list = list(x for x in object.get_netlists()) diff --git a/spydrnet/parsers/edif/tests/test_edif_parser.py b/spydrnet/parsers/edif/tests/test_edif_parser.py index 88841bcf..960aae9c 100644 --- a/spydrnet/parsers/edif/tests/test_edif_parser.py +++ b/spydrnet/parsers/edif/tests/test_edif_parser.py @@ -7,6 +7,7 @@ import tempfile import glob import shutil +from pathlib import Path class TestEdifTokenizer(unittest.TestCase): @@ -51,25 +52,26 @@ def test_multi_bit_add_out_of_order(self): @classmethod def setUpClass(cls) -> None: - cls.dir_of_edif_netlists = os.path.join(sdn.base_dir, "support_files", "EDIF_netlists") + cls.dir_of_edif_netlists = Path(sdn.base_dir, "support_files", "EDIF_netlists") cls.edif_files = sorted(glob.glob(os.path.join(cls.dir_of_edif_netlists, "*.edf.zip")), key=os.path.getsize) + # cls.edif_files = sorted(glob.glob(Path(cls.dir_of_edif_netlists, "*.edf.zip")), key=Path.stat().st_size) @unittest.skip("Test takes a long time right now.") def test_large_edif(self): for ii, filename in enumerate(self.edif_files): - if os.path.getsize(filename) <= 1024 * 10: + if Path(filename).stat().st_size <= 1024 * 10: continue self.ensure_cable_consistency(filename, ii, "edf") def test_small_edif_cables(self): for ii, filename in enumerate(self.edif_files): - if os.path.getsize(filename) > 1024 * 10: + if Path(filename).stat().st_size > 1024 * 10: continue self.ensure_cable_consistency(filename, ii, "edf") def ensure_cable_consistency(self,filename, ii, target_format_extension = None): with self.subTest(i=ii): - if os.path.exists("temp"): + if Path("temp").exists(): shutil.rmtree("temp") print(filename) with tempfile.TemporaryDirectory() as tempdirname: diff --git a/spydrnet/parsers/edif/tests/test_tokenizer.py b/spydrnet/parsers/edif/tests/test_tokenizer.py index e1480504..23166ead 100644 --- a/spydrnet/parsers/edif/tests/test_tokenizer.py +++ b/spydrnet/parsers/edif/tests/test_tokenizer.py @@ -3,6 +3,7 @@ import io import zipfile import tempfile +from pathlib import Path from spydrnet.parsers.edif.tokenizer import EdifTokenizer from spydrnet import base_dir @@ -12,10 +13,10 @@ def test_no_constructor_of_zero_argument(self): self.assertRaises(TypeError, EdifTokenizer) def test_stream(self): - dir_of_edif_netlists = os.path.join(base_dir, "support_files", "EDIF_netlists") - test_file = os.path.join(dir_of_edif_netlists, "n_bit_counter.edf.zip") + dir_of_edif_netlists = Path(base_dir, "support_files", "EDIF_netlists") + test_file = Path(dir_of_edif_netlists, "n_bit_counter.edf.zip") zip = zipfile.ZipFile(test_file) - file_name = os.path.basename(test_file) + file_name = Path(test_file).name file_name = file_name[:file_name.rindex(".")] stream = zip.open(file_name) stream = io.TextIOWrapper(stream) @@ -24,21 +25,22 @@ def test_stream(self): self.assertEqual("(", next_token) def test_open_zip_file(self): - dir_of_edif_netlists = os.path.join(base_dir, "support_files", "EDIF_netlists") + dir_of_edif_netlists = Path(base_dir, "support_files", "EDIF_netlists") test_file = os.path.join(dir_of_edif_netlists, "n_bit_counter.edf.zip") + # test_file = Path(dir_of_edif_netlists, "n_bit_counter.edf.zip") # UnicodeDecodeError tokenizer = EdifTokenizer.from_filename(test_file) next_token = tokenizer.next() self.assertEqual("(", next_token) def test_open_file(self): - dir_of_edif_netlists = os.path.join(base_dir, "support_files", "EDIF_netlists") - test_file = os.path.join(dir_of_edif_netlists, "n_bit_counter.edf.zip") - file_name = os.path.basename(test_file) + dir_of_edif_netlists = Path(base_dir, "support_files", "EDIF_netlists") + test_file = Path(dir_of_edif_netlists, "n_bit_counter.edf.zip") + file_name = Path(test_file).name file_name = file_name[:file_name.rindex(".")] zip = zipfile.ZipFile(test_file) with tempfile.TemporaryDirectory() as tempdir: zip.extract(file_name, tempdir) - extract_path = os.path.join(tempdir, file_name) + extract_path = Path(tempdir, file_name) tokenizer = EdifTokenizer.from_filename(extract_path) next_token = tokenizer.next() self.assertEqual("(", next_token) diff --git a/spydrnet/parsers/edif/tokenizer.py b/spydrnet/parsers/edif/tokenizer.py index e48e9f2c..ea9483ba 100644 --- a/spydrnet/parsers/edif/tokenizer.py +++ b/spydrnet/parsers/edif/tokenizer.py @@ -2,7 +2,7 @@ import re import zipfile import io -import os +from pathlib import Path class EdifTokenizer: @@ -30,13 +30,15 @@ def __init__(self, input_source): if isinstance(input_source, str): if zipfile.is_zipfile(input_source): zip = zipfile.ZipFile(input_source) - filename = os.path.basename(input_source) + filename = Path(input_source).name filename = filename[: filename.rindex(".")] stream = zip.open(filename) stream = io.TextIOWrapper(stream) self.input_stream = stream else: self.input_stream = open(input_source, "r") + elif isinstance(input_source, Path): + self.input_stream = open(input_source,"r") else: if isinstance(input_source, io.TextIOBase) is False: self.input_stream = io.TextIOWrapper(input_source) diff --git a/spydrnet/parsers/primitive_library_reader.py b/spydrnet/parsers/primitive_library_reader.py new file mode 100644 index 00000000..b7585414 --- /dev/null +++ b/spydrnet/parsers/primitive_library_reader.py @@ -0,0 +1,81 @@ +import spydrnet as sdn +from spydrnet.ir import Library +from spydrnet.parsers.verilog.parser import VerilogParser +import spydrnet.parsers.verilog.verilog_tokens as vt + +MODULE = "module" +END_MODULE = "endmodule" +INPUT = "input" +OUTPUT = "output" +PARAMETER = "parameter" + +class PrimitiveLibraryReader(): + """ + A class to extract primitive port information from a Verilog file and insert it into the netlist. The input file is parsed using the Verilog Parser and if any module information is found for a definition in the given netlist, the port information (i.e. directions) is added. + + parameters + ---------- + architecture - the targeted architecture. Must be a type from spydrnet.util.architecture + netlist - the current netlist + """ + + def __init__(self, architecture, netlist): + self.input_file = architecture + self.netlist = netlist + self.definition_list = list() + self.netlist_defs = dict() + self.parsed_defs = dict() + self.parser = None + + def run(self): + self.initialize() + while(self.parser.tokenizer.has_next()): + self.progress_past_comments() + self.parser.parse_primitive(definition_list=self.definition_list, bypass_name_check=True) + definition = self.parser.current_definition + if definition: + self.parsed_defs[definition.name] = definition + if len(self.parsed_defs) == len(self.netlist_defs): # found all needed information + break + cnt = self.insert_info() + print("Found information for %d definitions" % cnt) + + def initialize(self): + self.parser = VerilogParser.from_filename(self.input_file) + self.parser.initialize_tokenizer() + self.parser.current_library = Library() + self.create_defintiion_dict() + + def progress_past_comments(self): + token = self.parser.peek_token() + while(token != vt.MODULE and token != vt.PRIMITIVE): + # print(token) + self.parser.next_token() + token = self.parser.peek_token() + + def create_defintiion_dict(self): + for definition in self.netlist.get_definitions(): + self.netlist_defs[definition.name] = definition + self.definition_list.append(definition.name) + + def insert_info(self): + cnt = 0 + for def_name, definition in self.netlist_defs.items(): + if def_name in self.parsed_defs.keys(): + match = self.parsed_defs[def_name] + port_dict = self.create_port_dict(match) + # print(definition.name + " " + str(port_dict)) + for port in definition.get_ports(): + if port.name is None: # no port name, so assume it's the only port. + port.name = next(key for key in port_dict.keys()) + # print("Port name was None so changed to " + port.name) + port.direction = port_dict[port.name] + cnt+=1 + return cnt + + def create_port_dict(self, definition): + port_dict = dict() + for port in definition.get_ports(): + port_dict[port.name] = port.direction + # print(port.name + " has direction " + str(port.direction)) + return port_dict \ No newline at end of file diff --git a/spydrnet/parsers/tests/test_parsers.py b/spydrnet/parsers/tests/test_parsers.py index fad682ba..82930e0f 100644 --- a/spydrnet/parsers/tests/test_parsers.py +++ b/spydrnet/parsers/tests/test_parsers.py @@ -1,7 +1,49 @@ import unittest +import os import spydrnet as sdn +from spydrnet.util.netlist_type import VERILOG, EBLIF +from spydrnet.util.architecture import XILINX_7SERIES class TestParsers(unittest.TestCase): def test_parse(self): self.assertRaises(RuntimeError, sdn.parse, "fakefile.fakeext") + + + +class TestParseWithArchitecture(unittest.TestCase): + def test_verilog(self): + netlist = sdn.load_example_netlist_by_name("b13", VERILOG) + netlist.compose("b13.v", write_blackbox = False) + + netlist_1 = sdn.parse("b13.v") + for definition in netlist_1.get_definitions(): + if definition is not netlist_1.top_instance.reference: + for port in definition.get_ports(): + self.assertEqual(port.direction, sdn.UNDEFINED) + + netlist_2 = sdn.parse("b13.v", architecture=XILINX_7SERIES) + for definition in netlist_2.get_definitions(): + if definition is not netlist_2.top_instance.reference: + for port in definition.get_ports(): + self.assertNotEqual(port.direction, sdn.UNDEFINED, definition.name) + + os.remove("b13.v") + + def test_eblif(self): + netlist = sdn.load_example_netlist_by_name("toggle", EBLIF) + netlist.compose("toggle.eblif", write_blackbox = False) + + netlist_1 = sdn.parse("toggle.eblif") + for definition in netlist_1.get_definitions(): + if definition is not netlist_1.top_instance.reference and "logic-gate" not in definition.name: + for port in definition.get_ports(): + self.assertEqual(port.direction, sdn.UNDEFINED, definition.name) + + netlist_2 = sdn.parse("toggle.eblif", architecture=XILINX_7SERIES) + for definition in netlist_2.get_definitions(): + if definition is not netlist_2.top_instance.reference: + for port in definition.get_ports(): + self.assertNotEqual(port.direction, sdn.UNDEFINED, definition.name) + + os.remove("toggle.eblif") diff --git a/spydrnet/parsers/verilog/parser.py b/spydrnet/parsers/verilog/parser.py index 57d9acee..1983ea74 100644 --- a/spydrnet/parsers/verilog/parser.py +++ b/spydrnet/parsers/verilog/parser.py @@ -2,7 +2,7 @@ # please see the BYU CCl SpyDrNet license file for terms of usage. -from spydrnet.parsers.verilog.tokenizer import VerilogTokenizer +from spydrnet.parsers.verilog.tokenizer import VerilogTokenizer, VerilogTokenizerSimple import spydrnet.parsers.verilog.verilog_tokens as vt from spydrnet.ir import Netlist, Library, Definition, Port, Cable, Instance, OuterPin from spydrnet.plugins import namespace_manager @@ -104,6 +104,8 @@ def __init__(self): self.blackbox_holder = self.BlackboxHolder() + self.implicitly_mapped_ports = dict() + def parse(self): ''' parse a verilog netlist represented by verilog file @@ -115,7 +117,7 @@ def parse(self): self.parse_verilog() namespace_manager.default = ns_default self.tokenizer.__del__() - return self.netlist + return self.netlist def initialize_tokenizer(self): self.tokenizer = VerilogTokenizer(self.filename) @@ -173,7 +175,7 @@ def parse_verilog(self): self.netlist = sdn.Netlist() self.netlist.name = "SDN_VERILOG_NETLIST" self.work = self.netlist.create_library("work") - self.primitives = self.netlist.create_library("SDN.verilog_primitives") + self.primitives = self.netlist.create_library("hdi_primitives") self.current_library = self.work preprocessor_defines = set() @@ -239,6 +241,8 @@ def parse_verilog(self): "something at the top level of the file", "got unexpected token", token) self.add_blackbox_definitions() + + self.connect_implicitly_mapped_ports() return self.netlist @@ -248,16 +252,28 @@ def add_blackbox_definitions(self): d["VERILOG.primitive"] = True self.current_library.add_definition(d) - def parse_primitive(self): - '''similar to parse module but it will only look for the inputs and outputs to get an idea of how those things look''' + def parse_primitive(self, definition_list = [], bypass_name_check=False): + ''' + similar to parse module but it will only look for the inputs and outputs to get an idea of how those things look + definition_list is an optional parameter that is used by primitive_library_reader as primitive libraries are parsed. If the primitive name is not + in the definition list, it is not needed and will be skipped. + ''' token = self.next_token() assert token == vt.MODULE or token == vt.PRIMITIVE, self.error_string( vt.MODULE, "to begin module statement", token) token = self.next_token() - assert vt.is_valid_identifier(token), self.error_string( - "identifier", "not a valid module name", token) - name = token + if not bypass_name_check: + assert vt.is_valid_identifier(token), self.error_string( + "identifier", "not a valid module name", token) + name = token.strip() + + if definition_list: + if name not in definition_list: # we don't need this primitive info + while(token != vt.END_MODULE): + token = self.next_token() + # self.next_token() + return definition = self.blackbox_holder.get_blackbox(name) self.blackbox_holder.define(name) @@ -295,7 +311,7 @@ def parse_module(self): token = self.next_token() assert vt.is_valid_identifier(token), self.error_string( "identifier", "not a valid module name", token) - name = token + name = token.strip() definition = self.blackbox_holder.get_blackbox(name) self.blackbox_holder.define(name) @@ -361,7 +377,7 @@ def parse_module_header_parameters(self): token = self.next_token() assert vt.is_valid_identifier(token), self.error_string( 'identifer', "in parameter list", token) - key += token + key += token.strip() token = self.next_token() if key == vt.INTEGER: @@ -417,14 +433,14 @@ def parse_module_header_port_alias(self): example syntax .canale({\\canale[3] ,\\canale[2] ,\\canale[1] ,\\canale[0] }),''' - + token = self.next_token() assert token == vt.DOT, self.error_string( vt.DOT, "for port aliasing", token) token = self.next_token() assert vt.is_valid_identifier(token), self.error_string( "identifier", "for port in port aliasing", token) - name = token + name = token.strip() token = self.next_token() assert token == vt.OPEN_PARENTHESIS, self.error_string( @@ -445,12 +461,13 @@ def parse_module_header_port_alias(self): name, left_index=len(wires)-1, right_index=0) # connect the wires to the pins - assert len(port.pins) == len( wires), "Internal Error: the pins in a created port and the number of wires the aliased cable do not match up" - for i in range(len(port.pins)): - wires[i].connect_pin(port.pins[i]) + pin_list = list(p for p in port.pins) + pin_list.sort(reverse=True, key=self.pin_sort_func) + for i in range(len(pin_list)): + wires[i].connect_pin(pin_list[i]) def parse_cable_concatenation(self): '''parse a concatenation structure of cables, create the cables mentioned, and deal with indicies @@ -465,6 +482,7 @@ def parse_cable_concatenation(self): while token != vt.CLOSE_BRACE: cable, left, right = self.parse_variable_instantiation() wires_temp = self.get_wires_from_cable(cable, left, right) + wires_temp.sort(reverse=True,key=self.wire_sort_func) for w in wires_temp: wires.append(w) token = self.next_token() @@ -474,6 +492,9 @@ def parse_cable_concatenation(self): return wires + def wire_sort_func(self, w): + return w.cable.wires.index(w) + def parse_module_header_port(self): '''parse the port declaration in the module header''' token = self.peek_token() @@ -493,7 +514,7 @@ def parse_module_header_port(self): token = self.next_token() assert vt.is_valid_identifier(token), self.error_string( "identifier", "for port declaration", token) - name = token + name = token.strip() port = self.create_or_update_port( name, left_index=left, right_index=right, direction=direction, defining=defining) @@ -512,8 +533,11 @@ def parse_module_header_port(self): # wire together the cables and the port assert len(port.pins) == len(cable.wires), self.error_string( "the pins in a created port and the number of wires in it's cable do not match up", "wires: " + str(len(cable.wires)), "pins: " + str(len(port.pins))) - for i in range(len(port.pins)): - cable.wires[i].connect_pin(port.pins[i]) + + pin_list = list(p for p in port.pins) + pin_list.sort(reverse=False, key=self.pin_sort_func) + for i in range(len(pin_list)): + cable.wires[i].connect_pin(pin_list[i]) def parse_module_body(self): ''' @@ -584,12 +608,12 @@ def parse_port_declaration(self, properties): assert vt.is_valid_identifier(token), self.error_string( "port identifier", "identify port", token) names = [] - names.append(token) + names.append(token.strip()) token = self.next_token() while token == vt.COMMA: token = self.next_token() - names.append(token) + names.append(token.strip()) token = self.next_token() assert token == vt.SEMI_COLON, self.error_string( @@ -613,15 +637,16 @@ def parse_port_declaration(self, properties): else: port = self.create_or_update_port(port_list.pop( ).name, left_index=left, right_index=right, direction=direction, defining=True) - + if len(cable.wires) > 1: self.connect_resized_port_cable(cable, port) - def parse_cable_declaration(self, properties): - token = self.next_token() - assert token in [vt.REG, vt.WIRE, vt.TRI0, vt.TRI1], self.error_string( - "reg, tri1, tri0, or wire", "for cable declaration", token) - var_type = token + def parse_cable_declaration(self, properties, var_type = None): + if not var_type: + token = self.next_token() + assert token in [vt.REG, vt.WIRE, vt.TRI0, vt.TRI1], self.error_string( + "reg, tri1, tri0, or wire", "for cable declaration", token) + var_type = token token = self.peek_token() if token == vt.OPEN_BRACKET: @@ -633,21 +658,24 @@ def parse_cable_declaration(self, properties): token = self.next_token() assert vt.is_valid_identifier(token), self.error_string( "valid cable identifier", "identify the cable", token) - name = token + name = token.strip() cable = self.create_or_update_cable( name, left_index=left, right_index=right, var_type=var_type) cable["VERILOG.InlineConstraints"] = properties token = self.next_token() - assert token == vt.SEMI_COLON, self.error_string( - vt.SEMI_COLON, "to end cable declaration", token) + if token == vt.COMMA: # continue listing wires + self.parse_cable_declaration(dict(), var_type) + else: + assert token == vt.SEMI_COLON, self.error_string( + vt.SEMI_COLON, "to end cable declaration", token) def parse_instantiation(self, properties): token = self.next_token() assert vt.is_valid_identifier(token), self.error_string( "module identifier", "for instantiation", token) - def_name = token + def_name = token.strip() parameter_dict = dict() token = self.peek_token() @@ -657,7 +685,38 @@ def parse_instantiation(self, properties): token = self.next_token() assert vt.is_valid_identifier(token), self.error_string( "instance name", "for instantiation", token) - name = token + name = token.strip() + + # the current definition is instancing the current top instance, so a change needs to be made + if def_name == self.netlist.top_instance.reference.name: + # print(self.current_definition.name + " is instancing the current top instance (" + name+ " which is a "+ self.netlist.top_instance.reference.name+")") + old_top_instance = self.netlist.top_instance + + new_level = self.current_definition + # we know the current top is not right. So now we can move it up a level. + # But double check to make sure nothing is instancing the potential new top. + # Move up levels until we reach a new top + if (len(self.current_definition.references) > 0): + current_level = list(x for x in self.current_definition.references)[0] + while(True): + current_level = current_level.parent + try: + current_level.parent + except AttributeError: + new_level = current_level + break + + self.netlist.top_instance = sdn.Instance() + self.netlist.top_instance.name = new_level.name + "_top" + self.netlist.top_instance.reference = new_level + self.netlist.name = "SDN_VERILOG_NETLIST_" + new_level.name + + # print("New top instance is "+ self.netlist.top_instance.name) + + # this instance should just go away. It was created to be the top instance but we don't want that + # it has no parent. And now with no reference, it should have no ties to the netlist. + old_top_instance.reference = None + token = self.peek_token() assert token == vt.OPEN_PARENTHESIS, self.error_string( @@ -693,7 +752,7 @@ def parse_defparam_parameters(self): assert token == vt.DEFPARAM, self.error_string(vt.DEFPARAM, "to being defparam statement", token) token = self.next_token() assert vt.is_valid_identifier(token), self.error_string("valid identifier", "of an instance to apply the defparam to", token) - instance_name = token + instance_name = token.strip() if self.current_instance.name == instance_name: instance = self.current_instance else: @@ -707,6 +766,7 @@ def parse_defparam_parameters(self): assert token == vt.EQUAL, self.error_string(vt.EQUAL, "separate the key from the value in a defparam statement", token) token = self.next_token() value = token + params[key] = value token = self.next_token() assert token == vt.SEMI_COLON, self.error_string(vt.SEMI_COLON, "to end the defparam statement", token) self.set_instance_parameters(instance, params) @@ -722,12 +782,15 @@ def parse_parameter_mapping(self): assert token == vt.OPEN_PARENTHESIS, self.error_string( vt.OPEN_PARENTHESIS, "after # to begin parameter mapping", token) - while token != vt.CLOSE_PARENTHESIS: - k, v = self.parse_parameter_map_single() - params[k] = v + if self.peek_token() == vt.CLOSE_PARENTHESIS: # empty parameters token = self.next_token() - assert token in [vt.CLOSE_PARENTHESIS, vt.COMMA], self.error_string( - vt.COMMA + " or " + vt.CLOSE_PARENTHESIS, "to separate parameters or end parameter mapping", token) + else: + while token != vt.CLOSE_PARENTHESIS: + k, v = self.parse_parameter_map_single() + params[k] = v + token = self.next_token() + assert token in [vt.CLOSE_PARENTHESIS, vt.COMMA], self.error_string( + vt.COMMA + " or " + vt.CLOSE_PARENTHESIS, "to separate parameters or end parameter mapping", token) assert token == vt.CLOSE_PARENTHESIS, self.error_string( vt.CLOSE_PARENTHESIS, "to terminate ", token) @@ -743,7 +806,7 @@ def parse_parameter_map_single(self): token = self.next_token() assert vt.is_valid_identifier(token), self.error_string( "valid parameter identifier", "in parameter mapping", token) - k = token + k = token.strip() token = self.next_token() assert token == vt.OPEN_PARENTHESIS, self.error_string( @@ -763,11 +826,20 @@ def parse_port_mapping(self): assert token == vt.OPEN_PARENTHESIS, self.error_string( vt.OPEN_PARENTHESIS, "to start the port mapping", token) - while token != vt.CLOSE_PARENTHESIS: - self.parse_port_map_single() - token = self.next_token() - assert token in [vt.COMMA, vt.CLOSE_PARENTHESIS], self.error_string( - vt.COMMA + " or " + vt.CLOSE_PARENTHESIS, "between port mapping elements or to end the port mapping", token) + peeked_token = self.peek_token() + if peeked_token != vt.DOT: # the ports are implicitly mapped + token_list = list() + while (token != vt.CLOSE_PARENTHESIS): + token_list.append(token) + token = self.next_token() + token_list.append(token) + self.implicitly_mapped_ports[self.current_instance] = token_list + else: # the ports are explicitly mapped + while token != vt.CLOSE_PARENTHESIS: + self.parse_port_map_single() + token = self.next_token() + assert token in [vt.COMMA, vt.CLOSE_PARENTHESIS], self.error_string( + vt.COMMA + " or " + vt.CLOSE_PARENTHESIS, "between port mapping elements or to end the port mapping", token) def parse_port_map_single(self): '''acutally does the mapping of the pins''' @@ -778,7 +850,7 @@ def parse_port_map_single(self): token = self.next_token() assert vt.is_valid_identifier(token), self.error_string( "valid port identifier", "for port in instantiation port map", token) - port_name = token + port_name = token.strip() token = self.next_token() assert token == vt.OPEN_PARENTHESIS, self.error_string( @@ -801,8 +873,14 @@ def parse_port_map_single(self): "pins length to match or exceed cable.wires length", "INTERNAL ERROR", str(len(pins)) + "!=" + str(len(wires))) # there can be unconnected pins at the end of the port. + pins.sort(reverse=True, key=self.pin_sort_func) + # the offset makes sure connections are on lower + # end of the port for partially connected ports + offset = 0 + if len(pins) > len(wires): + offset = len(pins)-len(wires) for i in range(len(wires)): - wires[i].connect_pin(pins[i]) + wires[i].connect_pin(pins[offset + i]) token = self.next_token() @@ -814,6 +892,72 @@ def parse_port_map_single(self): assert token == vt.CLOSE_PARENTHESIS, self.error_string( vt.CLOSE_PARENTHESIS, "to end cable name in port mapping", token) + + def pin_sort_func(self, p): + if isinstance(p, sdn.OuterPin): + return p.inner_pin.port.pins.index(p.inner_pin) + return p.port.pins.index(p) + + def connect_implicitly_mapped_ports(self): + for instance, token_list in self.implicitly_mapped_ports.items(): + self.current_instance = instance + self.current_definition = instance.parent + port_list = list(x for x in instance.reference.get_ports()) + self.tokenizer = VerilogTokenizerSimple(token_list) + + token = self.next_token() + assert token == vt.OPEN_PARENTHESIS, self.error_string( + vt.OPEN_PARENTHESIS, "to encapsulate cable name in port mapping", token) + + index = 0 + + # There may be no mapped wires at all. It may be empty or filled with whitespace + token = self.peek_token() + + if token == vt.CLOSE_PARENTHESIS: + # Consume the token, we're going to skip the loop + token = self.next_token() + + while (token != vt.CLOSE_PARENTHESIS): + token = self.peek_token() + + if token == vt.OPEN_BRACE: + wires = self.parse_cable_concatenation() + else: + cable, left, right = self.parse_variable_instantiation() + wires = self.get_wires_from_cable(cable, left, right) + + if (index > len(port_list) - 1): # no port exists yet i.e. no module information in netlist + # print("Not enough ports for "+ instance.name) + port = instance.reference.create_port() + self.populate_new_port(port, None, len(wires)-1, 0, None) + else: + port = port_list[index] + + pins = list() + for pin in self.current_instance.pins: + if pin.inner_pin in port.pins: + pins.append(pin) + + assert len(pins) >= len(wires), self.error_string( + "pins length to match or exceed cable.wires length", "INTERNAL ERROR", str(len(pins)) + "!=" + str(len(wires))) + + # there can be unconnected pins at the end of the port. + pin_list = list(p for p in pins) + pin_list.sort(reverse=True, key=self.pin_sort_func) + # the offset makes sure connections are on lower + # end of the port for partially connected ports + offset = 0 + if len(pin_list) > len(wires): + offset = len(pin_list)-len(wires) + for i in range(len(wires)): + wires[i].connect_pin(pin_list[offset + i]) + + token = self.next_token() + index += 1 + + assert token == vt.CLOSE_PARENTHESIS, self.error_string( + vt.CLOSE_PARENTHESIS, "to end cable name in port mapping", token) def parse_assign(self): token = self.next_token() @@ -844,7 +988,7 @@ def parse_variable_instantiation(self): assert token[1] == vt.SINGLE_QUOTE, self.error_string(vt.SINGLE_QUOTE, "in the constant", token) assert token[2] == 'b', self.error_string('b', "in the constant", token) assert token[3] in ["0", "1", "x", "X", "z", "Z"], self.error_string("one of 0, 1, x, X, z, Z", "represent the constant value after '", token) - name = "\\ " + name = "\\ " elif vt.is_numeric(token[0]): assert False, self.error_string("single bit constant", "multibit constants not supported", token) else: @@ -858,7 +1002,7 @@ def parse_variable_instantiation(self): left, right = self.parse_brackets() cable = self.create_or_update_cable( - name, left_index=left, right_index=right) + name.strip(), left_index=left, right_index=right) return cable, left, right @@ -897,7 +1041,7 @@ def parse_star_property(self): token = self.next_token() while token != vt.STAR: assert vt.is_valid_identifier(token) - key = token + key = token.strip() token = self.next_token() assert token in [vt.EQUAL, vt.STAR, vt.COMMA], self.error_string( vt.EQUAL + " or " + vt.STAR + " or " + vt.COMMA, "to set a star parameter", token) @@ -947,11 +1091,12 @@ def get_assignment_definition(self, width): in_port.direction = sdn.Port.Direction.IN out_port.direction = sdn.Port.Direction.OUT - cable = definition.create_cable("through") - cable.create_wires(width) - for i in range(width): - cable.wires[i].connect_pin(in_port.pins[i]) - cable.wires[i].connect_pin(out_port.pins[i]) + # no need for this. It actually messes with other spydrnet functions like uniquify() and is_leaf() + # cable = definition.create_cable("through") + # cable.create_wires(width) + # for i in range(width): + # cable.wires[i].connect_pin(in_port.pins[i]) + # cable.wires[i].connect_pin(out_port.pins[i]) return definition @@ -1027,9 +1172,8 @@ def get_wires_from_cable(self, cable, left, right): left = left - cable.lower_index right = right - cable.lower_index temp_wires = cable.wires[min(left, right): max(left, right) + 1] - if left > right: - temp_wires = reversed(temp_wires) - + # if right > left: + temp_wires.reverse() for w in temp_wires: wires.append(w) @@ -1041,7 +1185,9 @@ def get_wires_from_cable(self, cable, left, right): wires.append(cable.wires[index]) else: - for w in cable.wires: + temp_wires = list(w for w in cable.wires) + temp_wires.reverse() + for w in temp_wires: wires.append(w) return wires @@ -1091,6 +1237,10 @@ def connect_resized_port_cable(self, resized_cable, resized_port): for i in range(len(resized_port.pins)): # I think these should be lined up right? if resized_port.pins[i] not in resized_cable.wires[i].pins: + if resized_port.pins[i].wire: + continue + # the resized_port's pin is already connected + # this likely occurred because of an alias statement. resized_cable.wires[i].connect_pin(resized_port.pins[i]) def create_or_update_cable(self, name, left_index=None, right_index=None, var_type=None, defining=False): @@ -1220,13 +1370,17 @@ def create_or_update_port(self, name, left_index=None, right_index=None, directi port_upper = port.lower_index + len(port.pins) - 1 if in_upper is not None and in_lower is not None: - - if in_lower < port_lower: - prepend = port_lower - in_lower - self.prepend_pins(port, prepend) - if in_upper > port_upper: - postpend = in_upper - port_upper - self.postpend_pins(port, postpend) + # to prevent unneccessary pins being added, check to see if port + # width is already correct + if (in_upper-in_lower) == (port_upper-port_lower): + None + else: + if in_lower < port_lower: + prepend = port_lower - in_lower + self.prepend_pins(port, prepend) + if in_upper > port_upper: + postpend = in_upper - port_upper + self.postpend_pins(port, postpend) if direction is not None: port.direction = direction diff --git a/spydrnet/parsers/verilog/tests/test_tokenizer.py b/spydrnet/parsers/verilog/tests/test_tokenizer.py index 15f944e1..d164a538 100644 --- a/spydrnet/parsers/verilog/tests/test_tokenizer.py +++ b/spydrnet/parsers/verilog/tests/test_tokenizer.py @@ -3,6 +3,7 @@ from spydrnet.parsers.verilog.tokenizer import * import spydrnet as sdn from io import StringIO +from pathlib import Path class TestVerilogTokenizer(unittest.TestCase): @@ -10,6 +11,8 @@ class TestVerilogTokenizer(unittest.TestCase): def test_against_4bit_adder(self): directory = os.path.join(sdn.base_dir, "support_files", "verilog_netlists", "4bitadder.v.zip") + # directory = Path(sdn.base_dir, "support_files", "verilog_netlists", "4bitadder.v.zip") # UnicodeDecodeError + tokenizer = VerilogTokenizer.from_filename(directory) while(tokenizer.has_next()): #print(tokenizer.next()) diff --git a/spydrnet/parsers/verilog/tests/test_verilogParser.py b/spydrnet/parsers/verilog/tests/test_verilogParser.py index 4fe1e4a5..178e0bf2 100644 --- a/spydrnet/parsers/verilog/tests/test_verilogParser.py +++ b/spydrnet/parsers/verilog/tests/test_verilogParser.py @@ -6,6 +6,7 @@ from spydrnet.parsers.verilog.parser import VerilogParser import spydrnet.parsers.verilog.verilog_tokens as vt from spydrnet import parsers +from spydrnet.util.selection import Selection import os class TestVerilogParser(unittest.TestCase): @@ -324,9 +325,9 @@ def test_port_resize_on_aliased_port(self): for c in parser.current_definition.cables: names.append(c.name) assert len(c.wires) == 1 - assert '\\byte_num[2] ' in names - assert '\\byte_num[1] ' in names - assert '\\byte_num[0] ' in names + assert '\\byte_num[2]' in names + assert '\\byte_num[1]' in names + assert '\\byte_num[0]' in names assert parser.current_definition.ports[0].name == 'byte_num' @@ -815,6 +816,69 @@ def test_parse_port_map_single(self): for p in parser.current_instance.pins: assert p in w_pins + def test_parse_implicitly_mapped_ports(self): + # create dummy netlist + to_write = "module top (input clk, output out);\n" + to_write += "\twire clk_c, VCC_net, out;\n" + to_write += "\tINST my_inst (clk_c, VCC_net, out);\n" + to_write += "endmodule\n\n" + to_write += "module INST (input port_0, input port_1, output port_2);\n" + to_write += "endmodule" + f = open("test_netlist.v", "x") + f.write(to_write) + f.close() + + parser = VerilogParser.from_filename("test_netlist.v") + parser.parse() + netlist = parser.netlist + + instance = next(netlist.get_instances("my_inst")) + self.assertEqual(instance.name, "my_inst") + connections = ["clk_c", "VCC_net", "out"] + for i in range(0,3): + port = next(instance.get_ports("port_"+str(i))) + self.assertEqual(len(port.pins), 1) + for pin in port.get_pins(selection=Selection.OUTSIDE): + self.assertEqual(pin.wire.cable.name, connections[i]) + + os.remove("test_netlist.v") + + def test_parse_empty_mapped_ports(self): + # create dummy netlist + to_write = "module top (input clk, output out);\n" + to_write += "\tINST my_inst ();\n" + to_write += "\tINST my_whitespace_inst ( );\n" + to_write += "endmodule\n\n" + to_write += "module INST (input port_0, input port_1, output port_2);\n" + to_write += "endmodule" + f = open("test_netlist.v", "x") + f.write(to_write) + f.close() + + parser = VerilogParser.from_filename("test_netlist.v") + parser.parse() + netlist = parser.netlist + + # my_inst has nothing attached + instance = next(netlist.get_instances("my_inst")) + self.assertEqual(instance.name, "my_inst") + for i in range(0,3): + port = next(instance.get_ports("port_"+str(i))) + self.assertEqual(len(port.pins), 1) + for pin in port.get_pins(selection=Selection.OUTSIDE): + self.assertEqual(pin.wire, None) + + # my_whitespace_inst has nothing attached and doesn't immediately have a closing paren + instance = next(netlist.get_instances("my_whitespace_inst")) + self.assertEqual(instance.name, "my_whitespace_inst") + for i in range(0,3): + port = next(instance.get_ports("port_"+str(i))) + self.assertEqual(len(port.pins), 1) + for pin in port.get_pins(selection=Selection.OUTSIDE): + self.assertEqual(pin.wire, None) + + os.remove("test_netlist.v") + ############################################################################ ##Port creation and modification ############################################################################ @@ -1135,10 +1199,8 @@ def test_create_new_assignment_definition(self): assert len(definition.ports) == 2 assert len(definition.ports[0].pins) == width assert len(definition.ports[1].pins) == width - assert len(definition.cables) == 1 - for i in range(width): - assert definition.ports[0].pins[i] in definition.cables[0].wires[i].pins - assert definition.ports[1].pins[i] in definition.cables[0].wires[i].pins + assert len(definition.cables) == 0 + def test_connect_assigned_wires(self): @@ -1180,7 +1242,7 @@ def test_parse_assign(self): assert i_right == None c1, o_left, o_right, c2, i_left, i_right = parser.parse_assign() assert c1.name == "SR2" - assert c2.name == "\\ " + assert c2.name == "\\" assert o_left == 2 assert o_right == None assert i_left == None @@ -1251,7 +1313,88 @@ def test_parser_star_list(self): assert "DONT_TOUCH" in stars2 assert stars2["DONT_TOUCH"] == None + ############################################ + ##test hierarchy + ############################################ + def test_hierarchy_fixing(self): + # create dummy netlist with the top instance coming after the lower instance + to_write = "module INST (input port_0, input port_1, output port_2);\n" + to_write += "endmodule\n\n" + to_write += "module top (input clk, output out);\n" + to_write += "\twire clk_c, VCC_net, out;\n" + to_write += "\tINST my_inst (clk_c, VCC_net, out);\n" + to_write += "endmodule\n" + f = open("test_netlist.v", "x") + f.write(to_write) + f.close() + + parser = VerilogParser.from_filename("test_netlist.v") + parser.parse() + netlist = parser.netlist + + self.assertEqual(netlist.top_instance.name, "top_top") + instance = next(netlist.get_instances("my_inst")) + self.assertTrue(instance.parent is netlist.top_instance.reference) + self.assertTrue(instance in netlist.top_instance.reference.children) + self.assertEqual(len(instance.reference.references), 1) + + os.remove("test_netlist.v") + + ################################################# + ## Other tests + ################################################# + + def test_partially_connected_ports(self): + # make sure partially connected ports connect pins on lower end of the port + # create a little netlist to use + to_write = "module top (input clk, output out);\n" + to_write += "\twire [6:0]count_cry, count_cry_2;\n" + to_write += "\twire [7:0] count_s, count_qxu;\n" + to_write += "\twire[7:0] count_s_2, count_qxu_2;\n" + to_write += "\twire [3:0] b;\n" + to_write += "\twire lopt_1, GND;\n" + to_write += "\n" + to_write += "\tCARRY4 carry_part_connected\n" + to_write += "\t(\n" + to_write += "\t\t.CI(count_cry[3]),\n" + to_write += "\t\t.CO(count_cry[6:4]),\n" + to_write += "\t\t.CYINIT(lopt_1),\n" + to_write += "\t\t.DI({GND, GND, GND}),\n" + to_write += "\t\t.O(count_s[7:4]),\n" + to_write += "\t\t.S(count_qxu[7:4])\n" + to_write += "\t);\n" + to_write += "\t\n" + to_write += "\tCARRY4 carry_full_connected\n" + to_write += "\t(\n" + to_write += "\t\t.CI(count_cry_2[3]),\n" + to_write += "\t\t.CO(count_cry_2[6:2]),\n" + to_write += "\t\t.CYINIT(lopt_1),\n" + to_write += "\t\t.DI({GND,GND, GND, GND}),\n" + to_write += "\t\t.O(count_s_2[7:4]),\n" + to_write += "\t\t.S(count_qxu_2[7:4])\n" + to_write += "\t);\n" + to_write += "endmodule\n" + f = open("test_netlist.v", "x") + f.write(to_write) + f.close() + + parser = VerilogParser.from_filename("test_netlist.v") + parser.parse() + netlist = parser.netlist + netlist = sdn.parse("test_netlist.v") + for inst in netlist.get_instances(): + for port in inst.get_ports(): + pins = list(p for p in port.get_pins(selection=Selection.OUTSIDE, filter=lambda x: x.instance==inst)) + pins.reverse() + if not all(p.wire for p in pins) and any(p.wire for p in pins): + wires = list(p.wire for p in pins if p.wire) + for i in range(len(wires)): + assert pins[i].wire, "The wire is " + str(pins[i].wire + " but should be connected") + for i in range(len(pins)-len(wires)): + assert pins[len(wires)+i].wire is None, "The wire is " + str(pins[i].wire + " but should NOT be connected") + + os.remove("test_netlist.v") ############################################ ##test helpers @@ -1308,6 +1451,9 @@ def test_get_wires_from_cable_helper(self): expected1 = [e11, [cable1.wires[0]], [cable1.wires[0]], [cable1.wires[1]], cable1.wires[2:5]] expected2 = [e21, [cable2.wires[0]], [cable2.wires[0]], [cable2.wires[1]], cable2.wires[2:5]] + expected1[4].reverse() + expected2[4].reverse() + for i in range(len(tests)): left1 = tests[i][0] + cable1.lower_index if tests[i][1] != None: @@ -1330,6 +1476,31 @@ def test_get_wires_from_cable_helper(self): w22 = expected2[i][j] assert w21 == w22, "the wires are not the same or not in the same order" + def test_constant_parsing(self): + """ + Tests multiple wire decalaration on the same line in verilog + """ + parser = VerilogParser() + + # Check constant 0 net declaration + tokens = ("1'b0", vt.SEMI_COLON) + tokenizer = self.TestTokenizer(tokens) + parser = VerilogParser() + parser.tokenizer = tokenizer + + parser.current_definition = sdn.Definition() + cable, _, _ = parser.parse_variable_instantiation() + self.assertEqual(cable.name, "\\", "Check const wire name") + + # Check constant 1 net declaration + tokens = ("1'b1", vt.SEMI_COLON) + tokenizer = self.TestTokenizer(tokens) + parser = VerilogParser() + parser.tokenizer = tokenizer + + parser.current_definition = sdn.Definition() + cable, _, _ = parser.parse_variable_instantiation() + self.assertEqual(cable.name, "\\", "Check const wire name") if __name__ == '__main__': diff --git a/spydrnet/parsers/verilog/tokenizer.py b/spydrnet/parsers/verilog/tokenizer.py index e75bbcd4..caa535ab 100644 --- a/spydrnet/parsers/verilog/tokenizer.py +++ b/spydrnet/parsers/verilog/tokenizer.py @@ -8,6 +8,7 @@ import os import spydrnet.parsers.verilog.verilog_tokens as vt from spydrnet.parsers.verilog.verilog_token_factory import TokenFactory +from pathlib import Path class VerilogTokenizer: @@ -35,13 +36,15 @@ def __init__(self, input_source): if isinstance(input_source, str): if zipfile.is_zipfile(input_source): zip = zipfile.ZipFile(input_source) - filename = os.path.basename(input_source) + filename = Path(input_source).name filename = filename[:filename.rindex(".")] stream = zip.open(filename) stream = io.TextIOWrapper(stream) self.input_stream = stream else: self.input_stream = open(input_source, 'r') + elif isinstance(input_source, Path): + self.input_stream = open(input_source,"r") else: if isinstance(input_source, io.TextIOBase) is False: self.input_stream = io.TextIOWrapper(input_source) @@ -105,3 +108,46 @@ def generate_tokens(self): def close(self): if self.input_stream: self.input_stream.close() + + +class VerilogTokenizerSimple(): + def __init__(self, token_list): + self.token = None + self.next_token = None + self.line_number = 0 + self.token_list = token_list + self.generator = self.generate_tokens() + + def __del__(self): + if hasattr(self, "input_stream"): + self.close() + + def generate_tokens(self): + for token in self.token_list: + yield token + + def has_next(self): + try: + self.peek() + return True + except StopIteration: + return False + + def next(self): + if self.next_token is not None: + self.token = self.next_token + self.next_token = None + else: + self.token = next(self.generator) + return self.token + + def peek(self): + if self.next_token is not None: + return self.next_token + else: + token = next(self.generator) + while len(token) >= 2 and (token[0:2] == vt.OPEN_LINE_COMMENT + or token[0:2] == vt.OPEN_BLOCK_COMMENT): + token = next(self.generator) + self.next_token = token + return self.next_token diff --git a/spydrnet/release.py b/spydrnet/release.py index 89e15962..3abfcb70 100644 --- a/spydrnet/release.py +++ b/spydrnet/release.py @@ -1,4 +1,5 @@ import os +from pathlib import Path # Version information name = 'spydrnet' @@ -49,14 +50,14 @@ ] -directory = os.path.dirname(os.path.abspath(__file__)) -version_filename = os.path.join(directory, "VERSION") +directory = Path(Path(__file__).absolute()).parent +version_filename = Path(directory, "VERSION") def load_versionfile(): global release global version global date - if os.path.isfile(version_filename): + if Path(version_filename).is_file(): with open(version_filename) as fi: release = fi.readline().strip()[1:] second_period_index = _get_second_period_index(release) @@ -89,12 +90,12 @@ def update_versionfile(): #check for git on path git_exists = False for path in os.environ['PATH'].split(os.pathsep): - exe_file = os.path.join(path, 'git') - if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK): + exe_file = Path(path, 'git') + if Path(exe_file).is_file() and os.access(exe_file, os.X_OK): git_exists = True else: - exe_file = os.path.join(path, 'git.exe') - if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK): + exe_file = Path(path, 'git.exe') + if Path(exe_file).is_file() and os.access(exe_file, os.X_OK): git_exists = True if git_exists: @@ -103,7 +104,7 @@ def update_versionfile(): git_version = git_describe_output.strip() if git_version.startswith('v'): - version_file = os.path.join(directory, "VERSION") + version_file = Path(directory, "VERSION") with open(version_file, 'w') as fh: fh.write(git_version + '\n') fh.write(date + '\n') diff --git a/spydrnet/support_files/architecture_libraries/f4pga_xilinx_7series.v.zip b/spydrnet/support_files/architecture_libraries/f4pga_xilinx_7series.v.zip new file mode 100644 index 00000000..26d8f648 Binary files /dev/null and b/spydrnet/support_files/architecture_libraries/f4pga_xilinx_7series.v.zip differ diff --git a/spydrnet/support_files/architecture_libraries/lifcl.v.zip b/spydrnet/support_files/architecture_libraries/lifcl.v.zip new file mode 100644 index 00000000..57a8ab7e Binary files /dev/null and b/spydrnet/support_files/architecture_libraries/lifcl.v.zip differ diff --git a/spydrnet/support_files/architecture_libraries/xilinx_7series.v.zip b/spydrnet/support_files/architecture_libraries/xilinx_7series.v.zip new file mode 100644 index 00000000..25d5bf7f Binary files /dev/null and b/spydrnet/support_files/architecture_libraries/xilinx_7series.v.zip differ diff --git a/spydrnet/support_files/architecture_libraries/yosys_internal_cells.v.zip b/spydrnet/support_files/architecture_libraries/yosys_internal_cells.v.zip new file mode 100644 index 00000000..046f467c Binary files /dev/null and b/spydrnet/support_files/architecture_libraries/yosys_internal_cells.v.zip differ diff --git a/spydrnet/tests/test_example_netlist_functionality.py b/spydrnet/tests/test_example_netlist_functionality.py index 988c09da..8e5f90e9 100644 --- a/spydrnet/tests/test_example_netlist_functionality.py +++ b/spydrnet/tests/test_example_netlist_functionality.py @@ -2,15 +2,18 @@ import spydrnet as sdn import glob import os +from pathlib import Path class TestExampleNetlistFunctionality(unittest.TestCase): def test_example_netlist_names(self): filenames = glob.glob(os.path.join(sdn.base_dir, 'support_files', 'EDIF_netlists', '*')) + # filenames = glob.glob(Path(sdn.base_dir, 'support_files', 'EDIF_netlists', '*')) # TypeError + self.assertEqual(len(filenames), len(sdn.example_netlist_names)) filenames.sort() for filename, example_name in zip(filenames, sdn.example_netlist_names): - basename = os.path.basename(filename) + basename = Path(filename).name example_name_golden = basename[:basename.index('.')] self.assertEqual(example_name, example_name_golden) diff --git a/spydrnet/util/architecture.py b/spydrnet/util/architecture.py new file mode 100644 index 00000000..db602ff7 --- /dev/null +++ b/spydrnet/util/architecture.py @@ -0,0 +1,10 @@ +import os +import spydrnet as sdn + +base_dir = os.path.dirname(os.path.abspath(sdn.__file__)) +base_file_path = os.path.join(base_dir, 'support_files', 'architecture_libraries') + +XILINX_7SERIES = os.path.join(base_file_path, "xilinx_7series.v.zip") +F4PGA_XILINX_7SERIES = os.path.join(base_file_path, "f4pga_xilinx_7series.v.zip") +LATTICE_LIFCL = os.path.join(base_file_path, "lifcl.v.zip") +YOSYS_CELLS = os.path.join(base_file_path, "yosys_internal_cells.v.zip") \ No newline at end of file diff --git a/spydrnet/util/tests/test_get_hwires.py b/spydrnet/util/tests/test_get_hwires.py index 14c2ba7f..51d24ec8 100644 --- a/spydrnet/util/tests/test_get_hwires.py +++ b/spydrnet/util/tests/test_get_hwires.py @@ -217,7 +217,7 @@ def test_through_hierarchy_again(self): # look at wire_name href = next(sdn.get_hwires(middle_floating_wire)) - assert ('middle/middle_cable[1]', href.name) + assert ('middle/middle_cable[1]' == href.name) hrefs = set(sdn.get_hwires(netlist.top_instance, recursive=True)) assert (href in hrefs)