diff --git a/Makefile b/Makefile index b7b4346..1d207d8 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ REQUIREMENTS_FILE := requirements.txt # https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html ENVIRONMENT_FILE := environment.yml -include third_party/make-env/conda.mk +-include third_party/make-env/conda.mk # Create a version.py file VERSION_PY = sphinxcontrib_hdl_diagrams/version.py diff --git a/README.rst b/README.rst index 5d08cfc..5ab465a 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ sphinxcontrib-hdl-diagrams ---- Sphinx Extension which generates various types of diagrams from HDL code, supporting Verilog, -nMigen and RTLIL. +nMigen, RTLIL, and VHDL. `sphinxcontrib-hdl-diagrams `_ is a Sphinx extension to make it easier to write nice documentation from @@ -84,19 +84,19 @@ Required .. |yosys| replace:: ``yosys`` .. _yosys: https://github.com/YosysHQ/yosys -By default, ``verilog-diagram`` uses the ``yowasp-yosys`` package provided in PyPI. +By default, ``hdl-diagram`` uses the ``yowasp-yosys`` package provided in PyPI. It can be installed by running ``pip install -r requirements.txt``. However, you could also use Yosys that is installed on your system, -or point to the specific Yosys binary using ``verilog_diagram_yosys`` variable +or point to the specific Yosys binary using ``hdl_diagram_yosys`` variable in the Sphinx ``conf.py`` file: To use Yosys that is available in your system, use the following setting:: - verilog_diagram_yosys = "system" + hdl_diagram_yosys = "system" If you want to point to the specific Yosys binary, provide the path to the program:: - verilog_diagram_yosys = "" + hdl_diagram_yosys = "" Optional ~~~~~~~~ @@ -106,6 +106,27 @@ Optional .. |netlistsvg| replace:: ``netlistsvg`` .. _netlistsvg: https://github.com/nturley/netlistsvg +* |ghdl|_ + +.. |ghdl| replace:: ``ghdl`` +.. _ghdl: https://github.com/ghdl/ghdl + +GHDL and ghdl-yosys-plugin are required for VHDL support. If ghdl-yosys-plugin is built into Yosys, +add this configuration option to let Yosys know:: + + hdl_diagram_ghdl = "built-in" + +Otherwise, to load GHDL as a runtime module, set this configuration option to:: + + hdl_diagram_ghdl = "module" + +Which will pass ``-m ghdl`` to Yosys when calling it. Similarly, setting this to the path of a +ghdl-yosys-plugin shared library will also work. + +Unfortunately, at this time GHDL and ghdl-yosys-plugin aren't supported by YoWASP. However, we'd +love to have it available. Are you aware of some proof-of-concept linking WASM compiled from both +C++ and Ada? Do you want to give it a try? Let us know! + Usage ----- diff --git a/docs/code/vhdl/alu.vhdl b/docs/code/vhdl/alu.vhdl new file mode 100644 index 0000000..0f707e1 --- /dev/null +++ b/docs/code/vhdl/alu.vhdl @@ -0,0 +1,40 @@ +-- +-- Copyright (C) 2020 The SymbiFlow Authors. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +-- SPDX-License-Identifier: Apache-2.0 + +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.numeric_std.all; + +entity alu is + port( + a : in unsigned(3 downto 0); + b : in unsigned(3 downto 0); + s : in unsigned(1 downto 0); + y : out unsigned(3 downto 0) + ); +end alu; + +architecture rtl of alu is + +begin + + y <= a when s="00" else + b when s="01" else + "0000" when s="10" else + a + b when s="11" else (others => '0'); + +end; diff --git a/docs/configuration/index.rst b/docs/configuration/index.rst new file mode 100644 index 0000000..54b0b67 --- /dev/null +++ b/docs/configuration/index.rst @@ -0,0 +1,82 @@ +Configuration +============= + +This is the list of possible configurations that go in ``conf.py``. + +Yosys ++++++ + +``hdl_diagram_yosys`` tells the program what version or binary of Yosys to use. +By default it is set to ``YoWASP``. Setting it to ``system``, the program will +use Yosys available in the current PATH. It can also contain the path to a specific +Yosys binary.:: + + hdl_diagram_yosys = "yowasp" # default + + hdl_diagram_yosys = "system" # use yosys from PATH + + hdl_diagram_yosys = "" # use specific yosys binary + + +netlistsvg +++++++++++ + +netlistsvg can take various skin files for use when creating diagrams. It is +set to ``default`` by default, using the built-in netlistsvg skin.:: + + hdl_diagram_skin = "" + + +Output format ++++++++++++++ + +The output format for the generated diagrams can either be set to ``svg`` or ``png``.:: + + hdl_diagram_output_format = "svg" + + hdl_diagram_output_format = "png" + + +GHDL +++++ + +ghdl-yosys-plugin can either be built into Yosys or loaded at runtime. If it is built into Yosys, +then set this configuration option to ``built-in``. If it is loaded at runtime, then this can +either be set to ``module`` if the shared library is located at ``YOSYS_PREFIX/share/yosys/plugins/ +ghdl.so``, or as a path to the ``ghdl.so`` shared library. :: + + hdl_diagram_ghdl = "built-in" # default, if ghdl-yosys-plugin is built into Yosys + + hdl_diagram_ghdl = "module" # passes `-m ghdl` to Yosys + + hdl_diagram_ghdl = "" # path to specific ghdl.so, + # passes `-m ''` to Yosys + +The VHDL standard used for GHDL can be set globally using this configuration option.:: + + hdl_diagram_ghdl_std = "08" # default, for VHDL 2008 + + hdl_diagram_ghdl_std = "97" # for VHDL 1993 + +Common Errors ++++++++++++++ + +.. code-block:: + + ERROR: + This version of Yosys cannot load plugins at runtime. + Some plugins may have been included at build time. + Use option `-H' to see the available built-in and plugin commands. + +This error signifies that the current version of Yosys cannot load plugins +at runtime, and so all plugins must be prebuit. For VHDL, ``hdl_diagram_ghdl`` +must be set to ``built-in``. + +.. code-block:: + + ERROR: Can't guess frontend for input file `' (missing -f option)! + +This error signifies that the version of Yosys being used cannot figure out +how to interpret the input file. For VHDL, this signifies that either GHDL +isn't being loaded properly, or that the current version of Yosys isn't compatible +with GHDL. diff --git a/docs/examples/alu-vhdl.rst b/docs/examples/alu-vhdl.rst new file mode 100644 index 0000000..c246a68 --- /dev/null +++ b/docs/examples/alu-vhdl.rst @@ -0,0 +1,89 @@ +4 bit ALU in VHDL +================= + +VHDL Code ++++++++++ + +RST Directive +************* + +.. code-block:: rst + :linenos: + + .. no-license:: ../code/vhdl/alu.vhdl + :language: vhdl + :linenos: + + +Result +****** + +.. no-license:: ../code/vhdl/alu.vhdl + :language: vhdl + :linenos: + +Yosys BlackBox Diagram +++++++++++++++++++++++ + +RST Directive +************* + +.. code-block:: rst + :linenos: + :emphasize-lines: 2 + + .. hdl-diagram:: ../code/vhdl/alu.vhdl + :type: yosys-bb + :module: alu + +Result +****** + +.. hdl-diagram:: ../code/vhdl/alu.vhdl + :type: yosys-bb + :module: alu + + +Yosys AIG Diagram ++++++++++++++++++ + +RST Directive +************* + +.. code-block:: rst + :linenos: + :emphasize-lines: 2 + + .. hdl-diagram:: ../code/vhdl/alu.vhdl + :type: yosys-aig + :module: alu + +Result +****** + +.. hdl-diagram:: ../code/vhdl/alu.vhdl + :type: yosys-aig + :module: alu + + +NetlistSVG Diagram +++++++++++++++++++ + +RST Directive +************* + +.. code-block:: rst + :linenos: + :emphasize-lines: 2 + + .. hdl-diagram:: ../code/vhdl/alu.vhdl + :type: netlistsvg + :module: alu + + +Result +****** + +.. hdl-diagram:: ../code/vhdl/alu.vhdl + :type: netlistsvg + :module: alu diff --git a/docs/examples/index.rst b/docs/examples/index.rst index f5c7f4b..e81b3ea 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -4,6 +4,7 @@ Examples .. toctree:: :maxdepth: 1 :glob: - + comb-full-adder - carry4 \ No newline at end of file + carry4 + alu-vhdl diff --git a/docs/index.rst b/docs/index.rst index 4fbb41d..92a1303 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,14 +3,14 @@ Sphinx HDL Diagrams sphinx-hdl-diagrams is an extension to Sphinx to make it easier to write nice documentation from HDL source files, in the form of Verilog, nMigen, -or RTLIL code. +RTLIL, or VHDL code. You use the |hdl-diagram|_ RST directive to generate various styles of diagrams from HDL code. -Most of the time there will be a license header at the top of source code, -which we might not want to show in the documentation. -This extension also provides the |no-license|_ RST directive which works exactly +Most of the time there will be a license header at the top of source code, +which we might not want to show in the documentation. +This extension also provides the |no-license|_ RST directive which works exactly like the `.. literalinclude` directive, but the `lines` option is overridden to only show the lines after the license header. @@ -56,6 +56,8 @@ conda `environment.yml `_ (required) - `netlistsvg `_ (optional) +- `GHDL `_ (required for VHDL) +- `ghdl-yosys-plugin `_ (required for VHDL) Usage ----- @@ -106,6 +108,7 @@ So, refer to `literalinclude` for the available options. :maxdepth: 1 :glob: :hidden: - + + configuration/index directives/index examples/index diff --git a/sphinxcontrib_hdl_diagrams/__init__.py b/sphinxcontrib_hdl_diagrams/__init__.py index 1fb0c53..5f7caf8 100644 --- a/sphinxcontrib_hdl_diagrams/__init__.py +++ b/sphinxcontrib_hdl_diagrams/__init__.py @@ -165,7 +165,9 @@ class HDLDiagram(Directive): "hdl_diagram_output_format": ["svg", "png"], "hdl_diagram_skin": ["default"], # or path "hdl_diagram_yosys_script": ["default"], # or path - "hdl_diagram_yosys": ["yowasp", "system"] # or path + "hdl_diagram_yosys": ["yowasp", "system"], # or path + "hdl_diagram_ghdl": ["built-in", "module"], # or path + "hdl_diagram_ghdl_std": ["87", "93", "93c", "00", "02", "08"] } def run(self): @@ -237,18 +239,20 @@ def run(self): return [node] -def run_yosys(src, cmd, yosys='yowasp'): +def run_yosys(src, cmd, yosys='yowasp', options=''): if yosys == 'yowasp': import yowasp_yosys ycmd = ["-q", "-p", "{}".format(cmd), src] + if options != '': + ycmd.insert(0, options) print("Running YoWASP yosys: {}".format(ycmd)) yowasp_yosys.run_yosys(ycmd) elif yosys == 'system': - ycmd = "yosys -p '{cmd}' {src}".format(src=src, cmd=cmd) + ycmd = "yosys {options} -p '{cmd}' {src}".format(options=options, src=src, cmd=cmd) print("Running yosys: {}".format(ycmd)) subprocess.check_output(ycmd, shell=True) else: - ycmd = "{yosys} -p '{cmd}' {src}".format(yosys=yosys, src=src, cmd=cmd) + ycmd = "{yosys} {options} -p '{cmd}' {src}".format(options=options, yosys=yosys, src=src, cmd=cmd) print("Running yosys: {}".format(ycmd)) subprocess.check_output(ycmd, shell=True) @@ -372,6 +376,28 @@ def nmigen_to_rtlil(fname, oname): cmd = "{python} {script} > {output}".format(python=sys.executable, script=fname, output=oname) subprocess.run(cmd, shell=True, check=True) +def vhdl_to_verilog(fname, oname, module, ghdl, ghdl_std, yosys): + assert os.path.exists(fname) + + if ghdl == "module": + yosys_opt = "-m ghdl " + elif ghdl == "built-in": + yosys_opt = "" + elif os.path.exists(ghdl): + yosys_opt = "-m '{}'".format(ghdl) + else: + raise HDLDiagramError("hdl_diagram_ghdl can only be \"module\", \"built-in\", or " + "a path to a ghdl-yosys-plugin shared library, not '{}'".format(ghdl)) + + output_dir = os.path.dirname(oname) + os.makedirs(output_dir, exist_ok=True) + cmd = "ghdl --std={std} {input} -e {module}; write_verilog {output}".format( + std=ghdl_std, + module=module, + input=fname, + output=oname + ) + run_yosys('', cmd, yosys, options=yosys_opt) def render_diagram(self, code, options, format, skin, yosys_script): # type: (nodes.NodeVisitor, unicode, Dict, unicode, unicode) -> Tuple[unicode, unicode] @@ -384,6 +410,8 @@ def render_diagram(self, code, options, format, skin, yosys_script): relfn = posixpath.join(self.builder.imgpath, fname) outfn = path.join(self.builder.outdir, self.builder.imagedir, fname) + yosys = self.builder.config.hdl_diagram_yosys + if source_ext == '.py': module = 'top' ilfn = path.join(self.builder.outdir, self.builder.imagedir, options['outname'] + '.il') @@ -391,9 +419,17 @@ def render_diagram(self, code, options, format, skin, yosys_script): source_path = ilfn elif source_ext == '.il' or source_ext == '.v': module = options['module'] + elif source_ext == '.vhd' or source_ext == '.vhdl': + if yosys == "yowasp": + raise HDLDiagramError("Cannot use YoWASP for VHDL (yet)") + module = options['module'] + ilfn = path.join(self.builder.outdir, self.builder.imagedir, options['outname'] + '.v') + vhdl_to_verilog(source_path, ilfn, module, + self.builder.config.hdl_diagram_ghdl, self.builder.config.hdl_diagram_ghdl_std, yosys) + source_path = ilfn else: raise HDLDiagramError("hdl_diagram_code file extension must be one of '.v', " - "'.il', or '.py', but is %r" % source_ext) + "'.il', '.py', '.vhd', or '.vhdl', but is %r" % source_ext) if path.isfile(outfn): print('Exiting file:', outfn) @@ -404,7 +440,6 @@ def render_diagram(self, code, options, format, skin, yosys_script): yosys_script = options['yosys_script'] if options['yosys_script'] is not None else yosys_script skin = options['skin'] if options['skin'] is not None else skin - yosys = self.builder.config.hdl_diagram_yosys yosys_options = HDLDiagram.global_variable_options["hdl_diagram_yosys"] if yosys not in yosys_options and not os.path.exists(yosys): raise HDLDiagramError("Yosys not found!") @@ -441,6 +476,9 @@ def render_diagram_html( self, node, code, options, imgcls=None, alt=None): # type: (nodes.NodeVisitor, hdl_diagram, unicode, Dict, unicode, unicode, unicode) -> Tuple[unicode, unicode] # NOQA + diagram_error = False + diagram_error_message = "" + yosys_script = self.builder.config.hdl_diagram_yosys_script if yosys_script != 'default' and not path.exists(yosys_script): raise HDLDiagramError("Yosys script file {} does not exist! Change hdl_diagram_yosys_script variable".format(yosys_script)) @@ -457,9 +495,16 @@ def render_diagram_html( fname, outfn = render_diagram(self, code, options, format, skin, yosys_script) except HDLDiagramError as exc: logger.warning('hdl_diagram code %r: ' % code + str(exc)) - raise nodes.SkipNode - - if fname is None: + diagram_error = True + diagram_error_message = str(exc) + # raise nodes.SkipNode + + if diagram_error: + self.body.append('
' + '{title}: ' + 'hdl_diagram code {file}: {message}' + '
'.format(title="Warning", file=code, message=diagram_error_message)) + elif fname is None: self.body.append(self.encode(code)) else: if alt is None: @@ -568,4 +613,6 @@ def setup(app): app.add_config_value('hdl_diagram_skin', 'default', 'html') app.add_config_value('hdl_diagram_yosys_script', 'default', 'html') app.add_config_value('hdl_diagram_yosys', 'yowasp', 'html') + app.add_config_value('hdl_diagram_ghdl', 'built-in', 'html') + app.add_config_value('hdl_diagram_ghdl_std', '08', 'html') return {'version': '1.0', 'parallel_read_safe': True}