Merge branch 'master' of https://github.com/YosysHQ/prjtrellis into facade
diff --git a/diamond.sh b/diamond.sh
index 6a69b2e..d54ee31 100755
--- a/diamond.sh
+++ b/diamond.sh
@@ -58,7 +58,7 @@
else
export LD_LIBRARY_PATH="${bindir}:${fpgabindir}"
fi
-export LM_LICENSE_FILE="${diamonddir}/license/license.dat"
+export LM_LICENSE_FILE="${LM_LICENSE_FILE:=${diamonddir}/license/license.dat}"
set -ex
if [[ $2 == *.ncl ]]
diff --git a/diamond_tcl.sh b/diamond_tcl.sh
index 0bbb4f6..36db79e 100755
--- a/diamond_tcl.sh
+++ b/diamond_tcl.sh
@@ -41,7 +41,7 @@
else
export LD_LIBRARY_PATH="${bindir}:${fpgabindir}"
fi
-export LM_LICENSE_FILE="${diamonddir}/license/license.dat"
+export LM_LICENSE_FILE="${LM_LICENSE_FILE:=${diamonddir}/license/license.dat}"
if $WINDOWS; then
$FOUNDRY/userware/NT/bin/nt64/ispTcl $1
diff --git a/docs/.gitignore b/docs/.gitignore
index 13856a1..e35d885 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -1,2 +1 @@
-venv
_build
diff --git a/docs/Makefile b/docs/Makefile
index cb6f107..b52c668 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -1,15 +1,13 @@
# Minimal makefile for Sphinx documentation
#
-MAKEDIR := $(dir $(lastword $(MAKEFILE_LIST)))
-
# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = [ -e venv/bin/activate ] && source venv/bin/activate; sphinx-build
-SPHINXAUTOBUILD = [ -e venv/bin/activate ] && source venv/bin/activate; sphinx-autobuild
-SPHINXPROJ = ProjectTrellis
-SOURCEDIR = .
-BUILDDIR = _build
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SPHINXAUTOBUILD = sphinx-autobuild
+SPHINXPROJ = ProjectX-Ray
+SOURCEDIR = .
+BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@@ -18,21 +16,9 @@
livehtml:
@$(SPHINXAUTOBUILD) -b html --ignore \*.swp --ignore \*~ $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html"
-.PHONY: help livehtml Makefile
-
-venv:
- rm -rf venv
- virtualenv --python=python3 venv
- source venv/bin/activate; pip install -r requirements.txt
-
-.PHONY: venv
-
-links:
- @true
-
-.PHONY: links
+.PHONY: help livereload Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile links
+%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/_static/.keepme b/docs/_static/.keepme
deleted file mode 100644
index e69de29..0000000
--- a/docs/_static/.keepme
+++ /dev/null
diff --git a/docs/conf.py b/docs/conf.py
index e847b58..8989469 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -24,7 +24,7 @@
import os
import sys
sys.path.insert(0, os.path.abspath('.'))
-from markdown_code_symlinks import LinkParser, MarkdownSymlinksDomain
+from markdown_code_symlinks import MarkdownCodeSymlinks
# -- General configuration ------------------------------------------------
@@ -36,13 +36,8 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
- 'sphinx.ext.autodoc',
- 'sphinx.ext.autosummary',
- 'sphinx.ext.doctest',
- 'sphinx.ext.imgmath',
- 'sphinx.ext.napoleon',
- 'sphinx.ext.todo',
- 'sphinx_markdown_tables',
+ 'sphinx.ext.imgmath', 'sphinx.ext.autodoc', 'sphinx.ext.doctest',
+ 'sphinx.ext.autosummary', 'sphinx.ext.napoleon', 'sphinx.ext.todo'
]
# Add any paths that contain templates here, relative to this directory.
@@ -52,7 +47,7 @@
# You can specify multiple suffix as a list of string:
source_suffix = ['.rst', '.md']
source_parsers = {
- '.md': 'markdown_code_symlinks.LinkParser',
+ '.md': 'recommonmark.parser.CommonMarkParser',
}
# The master toctree document.
@@ -63,24 +58,6 @@
copyright = u'2018, SymbiFlow Team'
author = u'SymbiFlow Team'
-# Enable github links when not on readthedocs
-on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
-if not on_rtd:
- html_context = {
- "display_github": True, # Integrate GitHub
- "github_user": "symbiflow", # Username
- "github_repo": "prjtrellis", # Repo name
- "github_version": "master", # Version
- "conf_py_path": "/doc/",
- }
-else:
- docs_dir = os.path.abspath(os.path.dirname(__file__))
- print("Docs dir is:", docs_dir)
- import subprocess
- subprocess.call('git fetch origin --unshallow', cwd=docs_dir, shell=True)
- subprocess.check_call('git fetch origin --tags', cwd=docs_dir, shell=True)
- subprocess.check_call('make links', cwd=docs_dir, shell=True)
-
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
@@ -100,10 +77,10 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = ['_build', 'venv', 'Thumbs.db', '.DS_Store']
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'default'
+pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
@@ -113,67 +90,24 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
-html_theme = 'sphinx_materialdesign_theme'
+html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
-html_theme_options = {
- # Specify a list of menu in Header.
- # Tuples forms:
- # ('Name', 'external url or path of pages in the document', boolean, 'icon name')
- #
- # Third argument:
- # True indicates an external link.
- # False indicates path of pages in the document.
- #
- # Fourth argument:
- # Specify the icon name.
- # For details see link.
- # https://material.io/icons/
- 'header_links': [
- ('Home', 'index', False, 'home'),
- ("GitHub", "https://github.com/SymbiFlow/prjtrellis", True, 'link')
- ],
+# html_theme_options = {}
- # Customize css colors.
- # For details see link.
- # https://getmdl.io/customize/index.html
- #
- # Values: amber, blue, brown, cyan deep_orange, deep_purple, green, grey, indigo, light_blue,
- # light_green, lime, orange, pink, purple, red, teal, yellow(Default: indigo)
- 'primary_color':
- 'deep_purple',
- # Values: Same as primary_color. (Default: pink)
- 'accent_color':
- 'purple',
-
- # Customize layout.
- # For details see link.
- # https://getmdl.io/components/index.html#layout-section
- 'fixed_drawer':
- True,
- 'fixed_header':
- True,
- 'header_waterfall':
- True,
- 'header_scroll':
- False,
-
- # Render title in header.
- # Values: True, False (Default: False)
- 'show_header_title':
- False,
- # Render title in drawer.
- # Values: True, False (Default: True)
- 'show_drawer_title':
- True,
- # Render footer.
- # Values: True, False (Default: True)
- 'show_footer':
- True
-}
+# Enable github links when not on readthedocs
+on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
+if not on_rtd:
+ html_context = {
+ "display_github": True, # Integrate GitHub
+ "github_user": "symbiflow", # Username
+ "github_repo": "prjtrellis", # Repo name
+ "github_version": "master", # Version
+ "conf_py_path": "/doc/",
+ }
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@@ -250,17 +184,9 @@
def setup(app):
- github_code_repo = 'https://github.com/SymbiFlow/prjtrellis/'
- github_code_branch = 'blob/master/'
-
- docs_root_dir = os.path.realpath(os.path.dirname(__file__))
- code_root_dir = os.path.realpath(os.path.join(docs_root_dir, ".."))
-
- MarkdownSymlinksDomain.init_domain(
- github_code_repo, github_code_branch, docs_root_dir, code_root_dir)
- MarkdownSymlinksDomain.find_links()
- app.add_domain(MarkdownSymlinksDomain)
+ MarkdownCodeSymlinks.find_links()
app.add_config_value(
'recommonmark_config', {
- 'github_code_repo': github_code_repo,
+ 'github_code_repo': 'https://github.com/SymbiFlow/prjtrellis',
}, True)
+ app.add_transform(MarkdownCodeSymlinks)
diff --git a/docs/markdown_code_symlinks.py b/docs/markdown_code_symlinks.py
new file mode 100644
index 0000000..1955493
--- /dev/null
+++ b/docs/markdown_code_symlinks.py
@@ -0,0 +1,179 @@
+import logging
+import os
+
+from recommonmark import transform
+"""
+Allow linking of Markdown documentation from the source code tree into the Sphinx
+documentation tree.
+
+The Markdown documents will have links relative to the source code root, rather
+than the place they are now linked too - this code fixes these paths up.
+
+We also want links from two Markdown documents found in the Sphinx docs to
+work, so that is also fixed up.
+"""
+
+
+def path_contains(parent_path, child_path):
+ """Check a path contains another path.
+
+ >>> path_contains("a/b", "a/b")
+ True
+ >>> path_contains("a/b", "a/b/")
+ True
+ >>> path_contains("a/b", "a/b/c")
+ True
+ >>> path_contains("a/b", "c")
+ False
+ >>> path_contains("a/b", "c/b")
+ False
+ >>> path_contains("a/b", "c/../a/b/d")
+ True
+ >>> path_contains("../a/b", "../a/b/d")
+ True
+ >>> path_contains("../a/b", "../a/c")
+ False
+ >>> path_contains("a", "abc")
+ False
+ >>> path_contains("aa", "abc")
+ False
+ """
+ # Append a separator to the end of both paths to work around the fact that
+ # os.path.commonprefix does character by character comparisons rather than
+ # path segment by path segment.
+ parent_path = os.path.join(os.path.normpath(parent_path), '')
+ child_path = os.path.join(os.path.normpath(child_path), '')
+ common_path = os.path.commonprefix((parent_path, child_path))
+ return common_path == parent_path
+
+
+def relative(parent_dir, child_path):
+ """Get the relative between a path that contains another path."""
+ child_dir = os.path.dirname(child_path)
+ assert path_contains(parent_dir, child_dir), "{} not inside {}".format(
+ child_path, parent_dir)
+ return os.path.relpath(child_path, start=parent_dir)
+
+
+class MarkdownCodeSymlinks(transform.AutoStructify, object):
+ docs_root_dir = os.path.realpath(os.path.dirname(__file__))
+ code_root_dir = os.path.realpath(os.path.join(docs_root_dir, "..", ".."))
+
+ mapping = {
+ 'docs2code': {},
+ 'code2docs': {},
+ }
+
+ @classmethod
+ def relative_code(cls, url):
+ """Get a value relative to the code directory."""
+ return relative(cls.code_root_dir, url)
+
+ @classmethod
+ def relative_docs(cls, url):
+ """Get a value relative to the docs directory."""
+ return relative(cls.docs_root_dir, url)
+
+ @classmethod
+ def add_mapping(cls, docs_rel, code_rel):
+ assert docs_rel not in cls.mapping['docs2code'], """\
+Assertion error! Document already in mapping!
+ New Value: {}
+Current Value: {}
+""".format(docs_rel, cls.mapping['docs2code'][docs_rel])
+ assert code_rel not in cls.mapping['code2docs'], """\
+Assertion error! Document already in mapping!
+ New Value: {}
+Current Value: {}
+""".format(docs_rel, cls.mapping['code2docs'][code_rel])
+
+ cls.mapping['docs2code'][docs_rel] = code_rel
+ cls.mapping['code2docs'][code_rel] = docs_rel
+
+ @classmethod
+ def find_links(cls):
+ """Walk the docs dir and find links to docs in the code dir."""
+ for root, dirs, files in os.walk(cls.docs_root_dir):
+ for fname in files:
+ fpath = os.path.abspath(os.path.join(root, fname))
+
+ if not os.path.islink(fpath):
+ continue
+
+ link_path = os.path.join(root, os.readlink(fpath))
+ # Is link outside the code directory?
+ if not path_contains(cls.code_root_dir, link_path):
+ continue
+
+ # Is link internal to the docs directory?
+ if path_contains(cls.docs_root_dir, link_path):
+ continue
+
+ docs_rel = cls.relative_docs(fpath)
+ code_rel = cls.relative_code(link_path)
+
+ cls.add_mapping(docs_rel, code_rel)
+ import pprint
+ pprint.pprint(cls.mapping)
+
+ @property
+ def url_resolver(self):
+ return self._url_resolver
+
+ @url_resolver.setter
+ def url_resolver(self, value):
+ print(self, value)
+
+ # Resolve a link from one markdown to another document.
+ def _url_resolver(self, ourl):
+ """Resolve a URL found in a markdown file."""
+ assert self.docs_root_dir == os.path.realpath(self.root_dir), """\
+Configuration error! Document Root != Current Root
+Document Root: {}
+ Current Root: {}
+""".format(self.docs_root_dir, self.root_dir)
+
+ src_path = os.path.abspath(self.document['source'])
+ src_dir = os.path.dirname(src_path)
+ dst_path = os.path.abspath(os.path.join(self.docs_root_dir, ourl))
+ dst_rsrc = os.path.relpath(dst_path, start=src_dir)
+
+ src_rdoc = self.relative_docs(src_path)
+
+ print
+ print("url_resolver")
+ print(src_path)
+ print(dst_path)
+ print(dst_rsrc)
+ print(src_rdoc)
+
+ # Is the source document a linked one?
+ if src_rdoc not in self.mapping['docs2code']:
+ # Don't do any rewriting on non-linked markdown.
+ url = ourl
+
+ # Is the destination also inside docs?
+ elif dst_rsrc not in self.mapping['code2docs']:
+ # Return a path to the GitHub repo.
+ url = "{}/blob/master/{}".format(
+ self.config['github_code_repo'], dst_rsrc)
+ else:
+ url = os.path.relpath(
+ os.path.join(
+ self.docs_root_dir, self.mapping['code2docs'][dst_rsrc]),
+ start=src_dir)
+ base_url, ext = os.path.splitext(url)
+ assert ext in (".md",
+ ".markdown"), ("Unknown extension {}".format(ext))
+ url = "{}.html".format(base_url)
+
+ print("---")
+ print(ourl)
+ print(url)
+ print
+ return url
+
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/docs/requirements.txt b/docs/requirements.txt
index ec9e872..56c1f78 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,13 +1,8 @@
-sphinx_materialdesign_theme
-
docutils
sphinx
sphinx-autobuild
breathe
recommonmark
-sphinx-markdown-tables
+sphinx_rtd_theme
sphinxcontrib-napoleon
-
-# Markdown cross-reference solver library
-git+https://github.com/SymbiFlow/sphinxcontrib-markdown-symlinks
diff --git a/examples/picorv32_tinyfpga/attosoc.v b/examples/picorv32_tinyfpga/attosoc.v
index 0b62a75..80b376f 100644
--- a/examples/picorv32_tinyfpga/attosoc.v
+++ b/examples/picorv32_tinyfpga/attosoc.v
@@ -1,8 +1,8 @@
/*
* ECP5 PicoRV32 demo
*
- * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
- * Copyright (C) 2018 David Shah <dave@ds0.me>
+ * Copyright (C) 2017 Claire Xenia Wolf <claire@yosyshq.com>
+ * Copyright (C) 2018 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/examples/picorv32_tinyfpga/picorv32.v b/examples/picorv32_tinyfpga/picorv32.v
index af634b4..35a8c74 100644
--- a/examples/picorv32_tinyfpga/picorv32.v
+++ b/examples/picorv32_tinyfpga/picorv32.v
@@ -1,7 +1,7 @@
/*
* PicoRV32 -- A Small RISC-V (RV32I) Processor Core
*
- * Copyright (C) 2015 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2015 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/examples/picorv32_ulx3s/attosoc.v b/examples/picorv32_ulx3s/attosoc.v
index 4921e29..39d5143 100644
--- a/examples/picorv32_ulx3s/attosoc.v
+++ b/examples/picorv32_ulx3s/attosoc.v
@@ -1,8 +1,8 @@
/*
* ECP5 PicoRV32 demo
*
- * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
- * Copyright (C) 2018 David Shah <dave@ds0.me>
+ * Copyright (C) 2017 Claire Xenia Wolf <claire@yosyshq.com>
+ * Copyright (C) 2018 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/examples/picorv32_ulx3s/picorv32.v b/examples/picorv32_ulx3s/picorv32.v
index af634b4..35a8c74 100644
--- a/examples/picorv32_ulx3s/picorv32.v
+++ b/examples/picorv32_ulx3s/picorv32.v
@@ -1,7 +1,7 @@
/*
* PicoRV32 -- A Small RISC-V (RV32I) Processor Core
*
- * Copyright (C) 2015 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2015 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/examples/picorv32_versa5g/attosoc.v b/examples/picorv32_versa5g/attosoc.v
index 4921e29..39d5143 100644
--- a/examples/picorv32_versa5g/attosoc.v
+++ b/examples/picorv32_versa5g/attosoc.v
@@ -1,8 +1,8 @@
/*
* ECP5 PicoRV32 demo
*
- * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
- * Copyright (C) 2018 David Shah <dave@ds0.me>
+ * Copyright (C) 2017 Claire Xenia Wolf <claire@yosyshq.com>
+ * Copyright (C) 2018 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/examples/picorv32_versa5g/picorv32.v b/examples/picorv32_versa5g/picorv32.v
index af634b4..35a8c74 100644
--- a/examples/picorv32_versa5g/picorv32.v
+++ b/examples/picorv32_versa5g/picorv32.v
@@ -1,7 +1,7 @@
/*
* PicoRV32 -- A Small RISC-V (RV32I) Processor Core
*
- * Copyright (C) 2015 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2015 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/examples/soc_ecp5_evn/attosoc.v b/examples/soc_ecp5_evn/attosoc.v
index 5761328..8fa54ae 100644
--- a/examples/soc_ecp5_evn/attosoc.v
+++ b/examples/soc_ecp5_evn/attosoc.v
@@ -1,8 +1,8 @@
/*
* ECP5 PicoRV32 demo
*
- * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
- * Copyright (C) 2018 David Shah <dave@ds0.me>
+ * Copyright (C) 2017 Claire Xenia Wolf <claire@yosyshq.com>
+ * Copyright (C) 2018 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/examples/soc_ecp5_evn/picorv32.v b/examples/soc_ecp5_evn/picorv32.v
index af634b4..35a8c74 100644
--- a/examples/soc_ecp5_evn/picorv32.v
+++ b/examples/soc_ecp5_evn/picorv32.v
@@ -1,7 +1,7 @@
/*
* PicoRV32 -- A Small RISC-V (RV32I) Processor Core
*
- * Copyright (C) 2015 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2015 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/examples/soc_ecp5_evn/simpleuart.v b/examples/soc_ecp5_evn/simpleuart.v
index 50808cb..eaff021 100644
--- a/examples/soc_ecp5_evn/simpleuart.v
+++ b/examples/soc_ecp5_evn/simpleuart.v
@@ -1,7 +1,7 @@
/*
* PicoSoC - A simple example SoC using PicoRV32
*
- * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2017 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/examples/soc_versa5g/attosoc.v b/examples/soc_versa5g/attosoc.v
index 0cc153d..48280b1 100644
--- a/examples/soc_versa5g/attosoc.v
+++ b/examples/soc_versa5g/attosoc.v
@@ -1,8 +1,8 @@
/*
* ECP5 PicoRV32 demo
*
- * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
- * Copyright (C) 2018 David Shah <dave@ds0.me>
+ * Copyright (C) 2017 Claire Xenia Wolf <claire@yosyshq.com>
+ * Copyright (C) 2018 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/examples/soc_versa5g/picorv32.v b/examples/soc_versa5g/picorv32.v
index af634b4..35a8c74 100644
--- a/examples/soc_versa5g/picorv32.v
+++ b/examples/soc_versa5g/picorv32.v
@@ -1,7 +1,7 @@
/*
* PicoRV32 -- A Small RISC-V (RV32I) Processor Core
*
- * Copyright (C) 2015 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2015 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/examples/soc_versa5g/simpleuart.v b/examples/soc_versa5g/simpleuart.v
index 50808cb..eaff021 100644
--- a/examples/soc_versa5g/simpleuart.v
+++ b/examples/soc_versa5g/simpleuart.v
@@ -1,7 +1,7 @@
/*
* PicoSoC - A simple example SoC using PicoRV32
*
- * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2017 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/libtrellis/CMakeLists.txt b/libtrellis/CMakeLists.txt
index d838d44..21b2ace 100644
--- a/libtrellis/CMakeLists.txt
+++ b/libtrellis/CMakeLists.txt
@@ -168,4 +168,6 @@
install(DIRECTORY ../database DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROGRAM_PREFIX}trellis PATTERN ".git" EXCLUDE)
install(DIRECTORY ../misc DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROGRAM_PREFIX}trellis)
install(DIRECTORY ../util/common DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROGRAM_PREFIX}trellis/util)
-install(DIRECTORY ../timing/util DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROGRAM_PREFIX}trellis/timing USE_SOURCE_PERMISSIONS)
+install(DIRECTORY ../timing/util DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROGRAM_PREFIX}trellis/timing)
+install(PROGRAMS ../timing/util/cell_html.py DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROGRAM_PREFIX}trellis/timing/util)
+install(PROGRAMS ../timing/util/cell_timings.py DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROGRAM_PREFIX}trellis/timing/util)
diff --git a/libtrellis/include/DatabasePath.hpp b/libtrellis/include/DatabasePath.hpp
index 288f670..8134323 100644
--- a/libtrellis/include/DatabasePath.hpp
+++ b/libtrellis/include/DatabasePath.hpp
@@ -32,7 +32,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/libtrellis/src/PyTrellis.cpp b/libtrellis/src/PyTrellis.cpp
index 38ea056..674f3fa 100644
--- a/libtrellis/src/PyTrellis.cpp
+++ b/libtrellis/src/PyTrellis.cpp
@@ -250,6 +250,7 @@
// From BitDatabase.cpp
class_<ConfigBit>(m, "ConfigBit")
+ .def(init<>())
.def_readwrite("frame", &ConfigBit::frame)
.def_readwrite("bit", &ConfigBit::bit)
.def_readwrite("inv", &ConfigBit::inv);
@@ -260,9 +261,11 @@
.def("__len__", [](const std::set<ConfigBit> &v) { return v.size(); })
.def("__iter__", [](std::set<ConfigBit> &v) {
return py::make_iterator(v.begin(), v.end());
- }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */
+ }, py::keep_alive<0, 1>()) /* Keep vector alive while iterator is used */
+ .def("add", [](std::set<ConfigBit> &v, const ConfigBit& value) { v.insert(value); });
class_<BitGroup>(m, "BitGroup")
+ .def(init<>())
.def(init<const CRAMDelta &>())
.def_readwrite("bits", &BitGroup::bits)
.def("match", &BitGroup::match)
@@ -273,6 +276,7 @@
py::bind_vector<vector<BitGroup>>(m, "BitGroupVector");
class_<ArcData>(m, "ArcData")
+ .def(init<>())
.def_readwrite("source", &ArcData::source)
.def_readwrite("sink", &ArcData::sink)
.def_readwrite("bits", &ArcData::bits);
@@ -287,6 +291,7 @@
.def("set_driver", &MuxBits::set_driver);
class_<WordSettingBits>(m, "WordSettingBits")
+ .def(init<>())
.def_readwrite("name", &WordSettingBits::name)
.def_readwrite("bits", &WordSettingBits::bits)
.def_readwrite("defval", &WordSettingBits::defval)
@@ -296,6 +301,7 @@
py::bind_map<map<string, BitGroup>>(m, "BitGroupMap");
class_<EnumSettingBits>(m, "EnumSettingBits")
+ .def(init<>())
.def_readwrite("name", &EnumSettingBits::name)
.def_readwrite("options", &EnumSettingBits::options)
.def("get_options", &EnumSettingBits::get_options)
@@ -356,6 +362,7 @@
py::bind_vector<vector<ConfigUnknown>>(m, "ConfigUnknownVector");
class_<TileConfig>(m, "TileConfig")
+ .def(init<>())
.def_readwrite("carcs", &TileConfig::carcs)
.def_readwrite("cwords", &TileConfig::cwords)
.def_readwrite("cenums", &TileConfig::cenums)
diff --git a/libtrellis/tools/ecpbram.cpp b/libtrellis/tools/ecpbram.cpp
index 7e424f1..0873d33 100644
--- a/libtrellis/tools/ecpbram.cpp
+++ b/libtrellis/tools/ecpbram.cpp
@@ -1,5 +1,5 @@
//
-// Copyright (C) 2016 Clifford Wolf <clifford@clifford.at>
+// Copyright (C) 2016 Claire Xenia Wolf <claire@yosyshq.com>
// Copyright (C) 2019 Sylvain Munaut <tnt@246tNt.com>
//
// Permission to use, copy, modify, and/or distribute this software for any
@@ -22,7 +22,7 @@
#include <stdint.h>
#ifdef _WIN32
#define NOMINMAX
-#include "Windows.h"
+#include "windows.h"
#undef NOMINMAX
#else
#include <unistd.h>
@@ -166,7 +166,7 @@
cerr << argv[0] << ": ECP5 BRAM content initialization tool" << endl;
cerr << endl;
cerr << "Copyright (C) 2019 Sylvain Munaut <tnt@246tNt.com>" << endl;
- cerr << "Copyright (C) 2016 Clifford Wolf <clifford@clifford.at>" << endl;
+ cerr << "Copyright (C) 2016 Claire Xenia Wolf <claire@yosyshq.com>" << endl;
cerr << endl;
cerr << options << endl;
return vm.count("help") ? 0 : 1;
diff --git a/libtrellis/tools/ecppack.cpp b/libtrellis/tools/ecppack.cpp
index f6bf902..ab560e0 100644
--- a/libtrellis/tools/ecppack.cpp
+++ b/libtrellis/tools/ecppack.cpp
@@ -85,7 +85,7 @@
cerr << "Version " << git_describe_str << endl;
cerr << argv[0] << ": ECP5 bitstream packer" << endl;
cerr << endl;
- cerr << "Copyright (C) 2018 David Shah <david@symbioticeda.com>" << endl;
+ cerr << "Copyright (C) 2018 gatecat <gatecat@ds0.me>" << endl;
cerr << endl;
cerr << "Usage: " << argv[0] << " input.config [output.bit] [options]" << endl;
cerr << options << endl;
diff --git a/libtrellis/tools/ecppll.cpp b/libtrellis/tools/ecppll.cpp
index 3764645..91a1aea 100644
--- a/libtrellis/tools/ecppll.cpp
+++ b/libtrellis/tools/ecppll.cpp
@@ -130,7 +130,7 @@
cerr << endl;
cerr << "This tool is experimental! Use at your own risk!" << endl;
cerr << endl;
- cerr << "Copyright (C) 2018-2019 David Shah <david@symbioticeda.com>" << endl;
+ cerr << "Copyright (C) 2018-2019 gatecat <gatecat@ds0.me>" << endl;
cerr << endl;
cerr << options << endl;
return vm.count("help") ? 0 : 1;
@@ -390,7 +390,7 @@
file << " output locked\n";
file << ");\n";
- if(params.internal_feedback)
+ if(params.internal_feedback || params.mode == pll_mode::HIGHRES)
file << "wire clkfb;\n";
if(params.dynamic)
{
@@ -398,7 +398,10 @@
file << "assign phasesel_hw = phasesel - 1;\n";
}
file << "(* FREQUENCY_PIN_CLKI=\"" << params.clkin_frequency << "\" *)\n";
- file << "(* FREQUENCY_PIN_CLKOP=\"" << params.fout << "\" *)\n";
+
+ if(params.mode != pll_mode::HIGHRES)
+ file << "(* FREQUENCY_PIN_CLKOP=\"" << params.fout << "\" *)\n";
+
if(params.secondary[0].enabled)
file << "(* FREQUENCY_PIN_CLKOS=\"" << params.secondary[0].freq << "\" *)\n";
if(params.secondary[1].enabled)
@@ -453,7 +456,12 @@
else
file << " .STDBY(1'b0),\n";
file << " .CLKI(" << params.clkin_name << "),\n";
- file << " .CLKOP(" << params.clkout0_name << "),\n";
+
+ if(params.mode == pll_mode::HIGHRES)
+ file << " .CLKOP(clkfb),\n";
+ else
+ file << " .CLKOP(" << params.clkout0_name << "),\n";
+
if(params.secondary[0].enabled){
if(params.mode == pll_mode::HIGHRES)
file << " .CLKOS(" << params.clkout0_name << "),\n";
@@ -466,16 +474,17 @@
if(params.secondary[2].enabled){
file << " .CLKOS3(" << params.secondary[2].name << "),\n";
}
- if(params.internal_feedback)
- {
+
+ if(params.internal_feedback || params.mode == pll_mode::HIGHRES)
file << " .CLKFB(clkfb),\n";
- file << " .CLKINTFB(clkfb),\n";
- }
else
- {
file << " .CLKFB(" << params.feedback_wname[params.feedback_clkout] << "),\n";
+
+ if(params.internal_feedback)
+ file << " .CLKINTFB(clkfb),\n";
+ else
file << " .CLKINTFB(),\n";
- }
+
if(params.dynamic)
{
file << " .PHASESEL0(phasesel_hw[0]),\n";
diff --git a/libtrellis/tools/ecpunpack.cpp b/libtrellis/tools/ecpunpack.cpp
index 1029356..5c9365b 100644
--- a/libtrellis/tools/ecpunpack.cpp
+++ b/libtrellis/tools/ecpunpack.cpp
@@ -54,7 +54,7 @@
cerr << "Version " << git_describe_str << endl;
cerr << argv[0] << ": ECP5 bitstream to text config converter" << endl;
cerr << endl;
- cerr << "Copyright (C) 2018 David Shah <david@symbioticeda.com>" << endl;
+ cerr << "Copyright (C) 2018 gatecat <gatecat@ds0.me>" << endl;
cerr << endl;
cerr << "Usage: " << argv[0] << " input.bit [output.config] [options]" << endl;
cerr << options << endl;
diff --git a/timing/resource/picorv32_large.v b/timing/resource/picorv32_large.v
index 7a5659f..5ad56e1 100644
--- a/timing/resource/picorv32_large.v
+++ b/timing/resource/picorv32_large.v
@@ -52,7 +52,7 @@
/*
* PicoRV32 -- A Small RISC-V (RV32I) Processor Core
*
- * Copyright (C) 2015 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2015 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/timing/resource/picorv32_x20.v b/timing/resource/picorv32_x20.v
index bb76434..20c4283 100644
--- a/timing/resource/picorv32_x20.v
+++ b/timing/resource/picorv32_x20.v
@@ -64,7 +64,7 @@
/*
* PicoRV32 -- A Small RISC-V (RV32I) Processor Core
*
- * Copyright (C) 2015 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2015 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/tools/ecp_vlog.py b/tools/ecp_vlog.py
new file mode 100644
index 0000000..f125770
--- /dev/null
+++ b/tools/ecp_vlog.py
@@ -0,0 +1,1046 @@
+import os
+import re
+import sys
+from collections import defaultdict
+from dataclasses import dataclass, field
+from functools import lru_cache
+from typing import Callable, ClassVar, Dict, List, Optional, Set, Tuple, Type
+
+try:
+ # optional import to get natural sorting of integers (i.e. 1, 5, 9, 10 instead of 1, 10, 5, 9)
+ from natsort import natsorted
+except ImportError:
+ natsorted = sorted
+
+import pytrellis
+import database
+
+
+# Conversions between tiles and locations
+@dataclass
+class TileData:
+ tile: pytrellis.Tile
+ cfg: pytrellis.TileConfig
+
+
+Location = Tuple[int, int] # pytrellis.Location cannot be used as a dictionary key
+TilesByLoc = Dict[Location, List[TileData]]
+
+
+def make_tiles_by_loc(chip: pytrellis.Chip) -> TilesByLoc:
+ tiles_by_loc: TilesByLoc = defaultdict(list)
+
+ for tilename, tile in chip.tiles.items():
+ locator = pytrellis.TileLocator(chip.info.family, chip.info.name, tile.info.type)
+ tilebitdb = pytrellis.get_tile_bitdata(locator)
+ tilecfg = tilebitdb.tile_cram_to_config(tile.cram)
+
+ rc = tile.info.get_row_col()
+ row, col = rc.first, rc.second
+ tileloc = pytrellis.Location(col, row)
+
+ tiles_by_loc[tileloc.x, tileloc.y].append(TileData(tile, tilecfg))
+
+ return tiles_by_loc
+
+
+# Utility classes representing a graph of configured connections
+@dataclass(eq=True, order=True, frozen=True)
+class Ident:
+ """ An identifier in the routing graph """
+
+ # place label first so we sort by identifier
+ label: str = field(compare=False)
+ # Idents are unique by ID so we only need to compare IDs
+ id: int = field(repr=False)
+ # Having a cache for Ident objects reduces memory pressure,
+ # speeds up Ident creation slightly, and significantly reduces
+ # the size of pickled graphs.
+ _cache: ClassVar[Dict[int, "Ident"]] = {}
+
+ @classmethod
+ def from_id(cls, rgraph: pytrellis.RoutingGraph, id: int) -> "Ident":
+ if id in cls._cache:
+ return cls._cache[id]
+ inst = Ident(rgraph.to_str(id), id)
+ cls._cache[id] = inst
+ return inst
+
+ @classmethod
+ def from_label(cls, rgraph: pytrellis.RoutingGraph, label: str) -> "Ident":
+ return cls.from_id(rgraph, rgraph.ident(label))
+
+ def __str__(self) -> str:
+ return self.label
+
+
+@dataclass(eq=True, order=True, frozen=True)
+class Node:
+ """ A node in the routing graph - either a wire or a BEL pin """
+
+ # put y first so we sort by row, then column
+ y: int
+ x: int
+ id: Ident
+ pin: Optional[Ident] = None
+ mod_name_map: ClassVar[Dict[str, str]] = {}
+
+ @property
+ def loc(self) -> pytrellis.Location:
+ return pytrellis.Location(self.x, self.y)
+
+ @property
+ def mod_name(self) -> str:
+ res = f"R{self.y}C{self.x}_{self.name}"
+ return res
+
+ @property
+ def name(self) -> str:
+ return self.id.label
+
+ @property
+ def pin_name(self) -> str:
+ if self.pin is None:
+ return ""
+ return self.pin.label
+
+ def __str__(self) -> str:
+ mod_name = self.mod_name
+ pin_name = self.pin_name
+ res = self.mod_name_map.get(mod_name, mod_name)
+ if pin_name:
+ res += "$" + pin_name
+ return res
+
+
+EdgeMap = Dict[Node, Set[Node]]
+
+
+@dataclass
+class Component:
+ graph: "ConnectionGraph"
+ nodes: Set[Node] = field(default_factory=set)
+
+ def get_roots(self) -> Set[Node]:
+ roots = set()
+ seen: Dict[Node, int] = {}
+
+ def visit(node: Node) -> None:
+ if node in seen:
+ if seen[node] == 0:
+ print(f"Warning: node {node} is part of a cycle!", file=sys.stderr)
+ return
+ seen[node] = 0
+ if not self.graph.edges_rev[node]:
+ roots.add(node)
+ else:
+ for x in self.graph.edges_rev[node]:
+ visit(x)
+ seen[node] = 1
+
+ for x in self.nodes:
+ visit(x)
+
+ return roots
+
+ def get_leaves(self) -> Set[Node]:
+ leaves = set()
+ seen: Dict[Node, int] = {}
+
+ def visit(node: Node) -> None:
+ if node in seen:
+ if seen[node] == 0:
+ print(f"Warning: node {node} is part of a cycle!", file=sys.stderr)
+ return
+ seen[node] = 0
+ if not self.graph.edges_fwd[node]:
+ leaves.add(node)
+ else:
+ for x in self.graph.edges_fwd[node]:
+ visit(x)
+ seen[node] = 1
+
+ for x in self.nodes:
+ visit(x)
+
+ return leaves
+
+
+@dataclass
+class ConnectionGraph:
+ """ A directed graph of Nodes. """
+
+ edges_fwd: EdgeMap = field(default_factory=lambda: defaultdict(set))
+ edges_rev: EdgeMap = field(default_factory=lambda: defaultdict(set))
+
+ def add_edge(self, source: Node, sink: Node) -> None:
+ self.edges_fwd[source].add(sink)
+ self.edges_rev[sink].add(source)
+
+ def get_components(self) -> List[Component]:
+ seen: Set[Node] = set()
+
+ def visit(node: Node, component: Component) -> None:
+ if node in seen:
+ return
+ seen.add(node)
+
+ component.nodes.add(node)
+ if node in self.edges_fwd:
+ for x in self.edges_fwd[node]:
+ visit(x, component)
+ if node in self.edges_rev:
+ for x in self.edges_rev[node]:
+ visit(x, component)
+
+ components: List[Component] = []
+ for edges in (self.edges_rev, self.edges_fwd):
+ for node in edges:
+ if node in seen:
+ continue
+ component = Component(self)
+ visit(node, component)
+ components.append(component)
+
+ return components
+
+
+# Connection graph generation
+def gen_config_graph(chip: pytrellis.Chip, rgraph: pytrellis.RoutingGraph, tiles_by_loc: TilesByLoc) -> ConnectionGraph:
+ @lru_cache(None)
+ def get_zero_bit_arcs(chip: pytrellis.Chip, tiletype: str) -> Dict[str, List[str]]:
+ """Get configurable zero-bit arcs from the given tile.
+
+ tile_cram_to_config ignores zero-bit arcs when generating the TileConfig,
+ which means that if all bits are unset for a given mux, no connection is
+ generated at all."""
+ locator = pytrellis.TileLocator(chip.info.family, chip.info.name, tiletype)
+ tilebitdb = pytrellis.get_tile_bitdata(locator)
+ arcs: Dict[str, List[str]] = defaultdict(list)
+ for sink in tilebitdb.get_sinks():
+ mux_data = tilebitdb.get_mux_data_for_sink(sink)
+ for arc_name, arc_data in mux_data.arcs.items():
+ if len(arc_data.bits.bits) == 0:
+ arcs[sink].append(arc_name)
+ return arcs
+
+ def bel_to_node(pos: Tuple[pytrellis.RoutingId, int]) -> Node:
+ rid, bel_pin = pos
+ id = Ident.from_id(rgraph, rid.id)
+ pin = Ident.from_id(rgraph, bel_pin)
+ return Node(x=rid.loc.x, y=rid.loc.y, id=id, pin=pin)
+
+ def wire_to_node(rid: pytrellis.RoutingId) -> Node:
+ id = Ident.from_id(rgraph, rid.id)
+ return Node(x=rid.loc.x, y=rid.loc.y, id=id)
+
+ def _get_enum_value(cfg: pytrellis.TileConfig, enum_name: str) -> Optional[str]:
+ for cenum in cfg.cenums:
+ if cenum.name == enum_name:
+ return cenum.value
+ return None
+
+ def _filter_data_pin(node: Node) -> bool:
+ # IOLOGIC[AC].[RT]XDATA[456] are mutually exclusive with IOLOGIC[BD].[RT]XDATA[0123],
+ # depending on whether 7:1 gearing is used, becacuse 7:1 gearing occupies two adjacent
+ # IOLOGIC units (A+B or C+D). Because they're mutually exclusive, some of the pins are
+ # hardwired together (e.g. 4A and 0B). To avoid a multi-root situation and spurious
+ # inputs/outputs, we need to pick which set to include based on the IO configuration.
+
+ bel_id = node.mod_name[-1]
+ assert bel_id in "ABCD"
+ pin_id = node.pin_name[-1]
+ assert pin_id in "0123456"
+
+ if bel_id in "AC" and pin_id in "0123":
+ # These pins are unconflicted
+ return True
+
+ if bel_id in "AB":
+ tiles = tiles_by_loc[node.x, node.y]
+ main_mod = "IOLOGICA"
+ else:
+ # HACK: The IOLOGICC enums seem to be the PIC[LR]2 tiles,
+ # which appear to always be exactly two tiles down from
+ # the PIC[LR]0 tiles where the actual pins are.
+ # This seems very fragile.
+ tiles = tiles_by_loc[node.x, node.y + 2]
+ main_mod = "IOLOGICC"
+
+ # Make sure we get the right tile on the tile location
+ for tiledata in tiles:
+ if any(site.type == main_mod for site in tiledata.tile.info.sites):
+ break
+ else:
+ print("error: could not locate IOLOGIC enums", file=sys.stderr)
+ return True
+
+ if node.pin_name.startswith("RX"):
+ is_71_mode = _get_enum_value(tiledata.cfg, main_mod + "IDDRXN.MODE") == "IDDR71"
+ else:
+ is_71_mode = _get_enum_value(tiledata.cfg, main_mod + "ODDRXN.MODE") == "ODDR71"
+
+ # Note that [456][BD] do not exist.
+ if pin_id in "456" and is_71_mode:
+ return True
+ elif pin_id in "0123" and not is_71_mode:
+ return True
+ return False
+
+ def add_edge(graph: ConnectionGraph, sourcenode: Node, sinknode: Node) -> None:
+ """ Add an edge subject to special-case filtering """
+
+ if re.match(r"^F[5X][ABCD]_SLICE$", sourcenode.name) and re.match(r"^F\d$", sinknode.name):
+ # Some of the -> Fn muxes use the same bits as the CCU2.INJECT enums.
+ # In CCU2 mode, these muxes should be fixed to Fn_SLICE -> Fn, and should
+ # not be set to F[5X] -> Fn no matter what the value of the mux bits are
+ # (since they represent CCU2_INJECT instead)
+ enum_name = f"SLICE{sourcenode.name[2]}.MODE"
+ for tiledata in tiles_by_loc[sourcenode.x, sinknode.y]:
+ if tiledata.tile.info.type.startswith("PLC2") and _get_enum_value(tiledata.cfg, enum_name) == "CCU2":
+ # CCU2: correct F[5X]n_SLICE connection to Fn_SLICE -> Fn
+ newsource = Ident.from_label(rgraph, sinknode.name + "_SLICE")
+ sourcenode = Node(x=sourcenode.x, y=sourcenode.y, id=newsource)
+ break
+ elif sourcenode.pin_name.startswith("RXDATA") and not _filter_data_pin(sourcenode):
+ # See comment in _filter_data_pin
+ return
+ elif sinknode.pin_name.startswith("TXDATA") and not _filter_data_pin(sinknode):
+ # See comment in _filter_data_pin
+ return
+
+ graph.add_edge(sourcenode, sinknode)
+
+ config_graph = ConnectionGraph()
+
+ for loc in tiles_by_loc:
+ rtile = rgraph.tiles[pytrellis.Location(loc[0], loc[1])]
+ for tiledata in tiles_by_loc[loc]:
+ tile = tiledata.tile
+ for arc in tiledata.cfg.carcs:
+ rarc = rtile.arcs[rgraph.ident(f"{arc.source}->{arc.sink}")]
+ sourcenode = wire_to_node(rarc.source)
+ sinknode = wire_to_node(rarc.sink)
+ add_edge(config_graph, sourcenode, sinknode)
+
+ # Expand configuration arcs to include BEL connections and zero-bit arcs
+ arc_graph = ConnectionGraph()
+ nodes_seen: Set[Node] = set()
+
+ def visit_node(node: Node, bel_func: Callable[[Node], None]) -> None:
+ """ Add unconfigurable or implicit arcs to the given node """
+ if node in nodes_seen:
+ return
+ nodes_seen.add(node)
+
+ try:
+ rtile = rgraph.tiles[node.loc]
+ rwire = rtile.wires[node.id.id]
+ except KeyError:
+ # there's a handful of troublesome cases which are outside of my control.
+ # Example: R0C31_G_ULDDRDEL does not exist; it's actually supposed to be the "fixed"
+ # connection G_ULDDRDEL=>DDRDEL but G_ULDDRDEL is not in the same tile.
+ print(f"Error: failed to find node {str(node)}", file=sys.stderr)
+ return
+
+ if node not in config_graph.edges_rev:
+ # Not configured - possible zero-bit configuration
+ for tiledata in tiles_by_loc[node.x, node.y]:
+ arcs = get_zero_bit_arcs(chip, tiledata.tile.info.type)
+ sources = arcs.get(node.id.label, [])
+ if not sources:
+ continue
+ for source in sources:
+ sourceid = Ident.from_label(rgraph, source)
+ sourcenode = Node(x=node.x, y=node.y, id=sourceid)
+ add_edge(arc_graph, sourcenode, node)
+ visit_node(sourcenode, bel_func)
+
+ # Add fixed connections
+ for bel in rwire.belsUphill:
+ add_edge(arc_graph, bel_to_node(bel), node)
+ bel_func(wire_to_node(bel[0]))
+ for bel in rwire.belsDownhill:
+ add_edge(arc_graph, node, bel_to_node(bel))
+ bel_func(wire_to_node(bel[0]))
+ for routes in [rwire.uphill, rwire.downhill]:
+ for rarcrid in routes:
+ rarcname = rgraph.to_str(rarcrid.id)
+ if "=>" in rarcname:
+ # => means a fixed (unconfigurable) connection
+ rarc = rgraph.tiles[rarcrid.loc].arcs[rarcrid.id]
+ sourcenode = wire_to_node(rarc.source)
+ sinknode = wire_to_node(rarc.sink)
+ add_edge(arc_graph, sourcenode, sinknode)
+ visit_node(sourcenode, bel_func)
+ visit_node(sinknode, bel_func)
+
+ # Add global (clock) connections - Project Trellis omits a lot of these :(
+ if node.name.startswith("G_HPBX"):
+ # TAP_DRIVE -> PLB tile
+ tap = chip.global_data.get_tap_driver(node.y, node.x)
+ if tap.dir == pytrellis.TapDir.LEFT:
+ tap_name = node.name.replace("G_HPBX", "L_HPBX")
+ else:
+ tap_name = node.name.replace("G_HPBX", "R_HPBX")
+ tap_id = Ident.from_label(rgraph, tap_name)
+ tap_node = Node(x=tap.col, y=node.y, id=tap_id)
+ add_edge(arc_graph, tap_node, node)
+ visit_node(tap_node, bel_func)
+
+ elif node.name.startswith("G_VPTX"):
+ # Spine tile -> TAP_DRIVE
+ tap = chip.global_data.get_tap_driver(node.y, node.x)
+ if tap.col == node.x:
+ # Spine output
+ quadrant = chip.global_data.get_quadrant(node.y, node.x)
+ spine = chip.global_data.get_spine_driver(quadrant, node.x)
+ spine_node = Node(x=spine.second, y=spine.first, id=node.id)
+ add_edge(arc_graph, spine_node, node)
+ visit_node(spine_node, bel_func)
+
+ elif node.name.startswith("G_HPRX"):
+ # Center mux -> spine tile (qqPCLKn -> G_HPRXnn00)
+ quadrant = chip.global_data.get_quadrant(node.y, node.x)
+ assert node.name.endswith("00")
+ clkid = int(node.name[6:-2])
+ global_id = Ident.from_label(rgraph, f"G_{quadrant}PCLK{clkid}")
+ global_node = Node(x=0, y=0, id=global_id)
+ add_edge(arc_graph, global_node, node)
+ visit_node(global_node, bel_func)
+
+ # Visit every configured arc and record all BELs seen
+ bels_todo: Set[Node] = set()
+ for sourcenode, nodes in config_graph.edges_fwd.items():
+ for sinknode in nodes:
+ add_edge(arc_graph, sourcenode, sinknode)
+ visit_node(sourcenode, bels_todo.add)
+ visit_node(sinknode, bels_todo.add)
+
+ # Adding *every* fixed connection is too expensive.
+ # As a compromise, add any fixed connection connected
+ # to used BELs. Ignore BELs that don't have any configured
+ # arcs.
+ for node in bels_todo:
+ rtile = rgraph.tiles[node.loc]
+ for _, rwire in rtile.wires.items():
+ wireident = Ident.from_id(rgraph, rwire.id)
+ wirenode = Node(x=node.x, y=node.y, id=wireident)
+ for bel in rwire.belsUphill:
+ if bel[0].id == node.id.id:
+ add_edge(arc_graph, bel_to_node(bel), wirenode)
+ visit_node(wirenode, lambda node: None)
+ for bel in rwire.belsDownhill:
+ if bel[0].id == node.id.id:
+ add_edge(arc_graph, wirenode, bel_to_node(bel))
+ visit_node(wirenode, lambda node: None)
+
+ return arc_graph
+
+
+# Verilog generation
+def filter_node(node: Node) -> bool:
+ if node.pin is None:
+ # We assume that all *useful* wires go between BELs.
+ return False
+ if "_ECLKSYNC" in node.mod_name:
+ # ECLKSYNC BELs appear to basically coincide with ECLKBUF BELs, making them redundant
+ # for the purposes of Verilog generation.
+ return False
+ if node.pin_name.startswith("IOLDO") or node.pin_name.startswith("IOLTO"):
+ # IOLDO/IOLTO are for internal use:
+ # https://freenode.irclog.whitequark.org/~h~openfpga/2018-12-25#23748701;
+ # 07:55 <daveshah> kbeckmann: IOLDO and IOLTO are for internal use only
+ # 07:55 <daveshah> They are for the dedicated interconnect between IOLOGIC and PIO
+ # Since we don't currently implement I/O modules, these pins do not
+ # need to be exported to Verilog.
+ return False
+ if node.pin_name == "INDD":
+ # INDD is the input after the delay block. This is currently redundant because
+ # the input source (PIO$O) will be exposed as an independent input, so the module's
+ # caller can simply hard-code an appropriate delay to the module input.
+ # If the I/O modules are ever implemented, it will be necessary to disambiguate
+ # PIO$O from INDD for the IOLOGIC$DI input to avoid a multi-root situation.
+ return False
+ return True
+
+
+@dataclass
+class Module:
+ """ A class to encapsulate a synthesized BEL supported by simulation """
+
+ module_name: str
+ tiledata: TileData
+ pin_map: Dict[str, Node]
+
+ input_pins: ClassVar[List[str]] = []
+ output_pins: ClassVar[List[str]] = []
+
+ @classmethod
+ def create_from_node(cls, node: Node, tiles_by_loc: TilesByLoc) -> Optional["Module"]:
+ modcls: Type[Module]
+ if node.name.startswith("SLICE"):
+ modcls = SliceModule
+ tiletype = "PLC2"
+ elif node.name.startswith("EBR"):
+ modcls = EBRModule
+ tiletype = "MIB_EBR"
+ else:
+ return None
+
+ for tiledata in tiles_by_loc[node.x, node.y]:
+ if tiledata.tile.info.type.startswith(tiletype):
+ break
+ else:
+ raise Exception(f"Tile type {tiletype} not found for node {node}")
+
+ return modcls(node.name, tiledata, {})
+
+ @classmethod
+ def print_definition(cls) -> None:
+ """ Print the Verilog code for the module definition """
+ raise NotImplementedError()
+
+ def _print_parameters(self, param_renames: Dict[str, str]) -> None:
+ """ Print the BEL's enums and words as an instance parameter list """
+ strs: List[str] = []
+
+ # Dump enumerations in Verilog-compatible format
+ for e in self.tiledata.cfg.cenums:
+ bel, ename = e.name.split(".", 1)
+ ename = ename.replace(".", "_")
+ ename = param_renames.get(ename, ename)
+ if bel == self.module_name:
+ strs.append(f' .{ename}("{e.value}")')
+ # Dump initialization words in Verilog format
+ for w in self.tiledata.cfg.cwords:
+ bel, ename = w.name.split(".", 1)
+ ename = ename.replace(".", "_")
+ ename = param_renames.get(ename, ename)
+ if bel == self.module_name:
+ value = [str(int(c)) for c in w.value]
+ valuestr = "".join(value[::-1])
+ strs.append(f" .{ename}({len(value)}'b{valuestr})")
+
+ if strs:
+ print(",\n".join(strs))
+
+ def _print_pins(self) -> None:
+ """ Print the BEL's enums and words as an instance parameter list """
+ strs: List[str] = []
+
+ # Dump input/output pins (already referenced to root pins), inputs first
+ pin_map_pins = set(self.pin_map.keys())
+ all_input_pins = set(self.input_pins)
+ output_pins = natsorted(pin_map_pins - all_input_pins)
+ input_pins = natsorted(pin_map_pins & all_input_pins)
+ for pin in input_pins + output_pins:
+ strs.append(f" .{pin}( {self.pin_map[pin]} )")
+
+ if strs:
+ print(",\n".join(strs))
+
+ def print_instance(self, instname: str) -> None:
+ """ Print the Verilog code for this specific module instance """
+ raise NotImplementedError()
+
+
+@dataclass
+class SliceModule(Module):
+ input_pins: ClassVar[List[str]] = [
+ "A0",
+ "B0",
+ "C0",
+ "D0",
+ "A1",
+ "B1",
+ "C1",
+ "D1",
+ "M0",
+ "M1",
+ "FCI",
+ "FXA",
+ "FXB",
+ "CLK",
+ "LSR",
+ "CE",
+ "DI0",
+ "DI1",
+ "WD0",
+ "WD1",
+ "WAD0",
+ "WAD1",
+ "WAD2",
+ "WAD3",
+ "WRE",
+ "WCK",
+ ]
+
+ output_pins: ClassVar[List[str]] = [
+ "F0",
+ "Q0",
+ "F1",
+ "Q1",
+ "FCO",
+ "OFX0",
+ "OFX1",
+ "WDO0",
+ "WDO1",
+ "WDO2",
+ "WDO3",
+ "WADO0",
+ "WADO1",
+ "WADO2",
+ "WADO3",
+ ]
+
+ @classmethod
+ def print_definition(cls) -> None:
+ """ Print the Verilog code for the module definition """
+ params = [
+ "MODE",
+ "GSR",
+ "SRMODE",
+ "CEMUX",
+ "CLKMUX",
+ "LSRMUX",
+ "LUT0_INITVAL",
+ "LUT1_INITVAL",
+ "REG0_SD",
+ "REG1_SD",
+ "REG0_REGSET",
+ "REG1_REGSET",
+ "REG0_LSRMODE",
+ "REG1_LSRMODE",
+ "CCU2_INJECT1_0",
+ "CCU2_INJECT1_1",
+ "WREMUX",
+ "WCKMUX",
+ "A0MUX",
+ "A1MUX",
+ "B0MUX",
+ "B1MUX",
+ "C0MUX",
+ "C1MUX",
+ "D0MUX",
+ "D1MUX",
+ ]
+
+ print(
+ f"""
+/* This module requires the cells_sim library from yosys/techlibs/ecp5/cells.sim.v
+ for the TRELLIS_SLICE definition. Include that cell library before including this
+ file. */
+module ECP5_SLICE(
+ input {", ".join(cls.input_pins)},
+ output {", ".join(cls.output_pins)}
+);
+
+ /* These defaults correspond to all-zero-bit enumeration values */
+ parameter MODE = "LOGIC";
+ parameter GSR = "ENABLED";
+ parameter SRMODE = "LSR_OVER_CE";
+ parameter [127:0] CEMUX = "CE";
+ parameter CLKMUX = "CLK";
+ parameter LSRMUX = "LSR";
+ parameter LUT0_INITVAL = 16'hFFFF;
+ parameter LUT1_INITVAL = 16'hFFFF;
+ parameter REG0_SD = "1";
+ parameter REG1_SD = "1";
+ parameter REG0_REGSET = "SET";
+ parameter REG1_REGSET = "SET";
+ parameter REG0_LSRMODE = "LSR";
+ parameter REG1_LSRMODE = "LSR";
+ parameter [127:0] CCU2_INJECT1_0 = "YES";
+ parameter [127:0] CCU2_INJECT1_1 = "YES";
+ parameter WREMUX = "WRE";
+ parameter WCKMUX = "WCK";
+
+ parameter A0MUX = "A0";
+ parameter A1MUX = "A1";
+ parameter B0MUX = "B0";
+ parameter B1MUX = "B1";
+ parameter C0MUX = "C0";
+ parameter C1MUX = "C1";
+ parameter D0MUX = "D0";
+ parameter D1MUX = "D1";
+
+ TRELLIS_SLICE #(
+ {", ".join(f".{param}({param})" for param in params)}
+ ) impl (
+ {", ".join(f".{pin}({pin})" for pin in cls.input_pins)},
+ {", ".join(f".{pin}({pin})" for pin in cls.output_pins)}
+ );
+endmodule
+""".strip()
+ )
+
+ def print_instance(self, instname: str) -> None:
+ print("ECP5_SLICE #(")
+ self._print_parameters(
+ {
+ "K0_INIT": "LUT0_INITVAL",
+ "K1_INIT": "LUT1_INITVAL",
+ }
+ )
+ print(f") {instname} (")
+ self._print_pins()
+ print(");")
+ print()
+
+
+class EBRModule(Module):
+ input_pins: ClassVar[List[str]] = [
+ # Byte Enable wires
+ "ADA0",
+ "ADA1",
+ "ADA2",
+ "ADA3",
+ # ADW
+ "ADA5",
+ "ADA6",
+ "ADA7",
+ "ADA8",
+ "ADA9",
+ "ADA10",
+ "ADA11",
+ "ADA12",
+ "ADA13",
+ # ADR
+ "ADB5",
+ "ADB6",
+ "ADB7",
+ "ADB8",
+ "ADB9",
+ "ADB10",
+ "ADB11",
+ "ADB12",
+ "ADB13",
+ "CEB", # CER
+ "CLKA", # CLKW
+ "CLKB", # CLKR
+ # DI
+ "DIA0",
+ "DIA1",
+ "DIA2",
+ "DIA3",
+ "DIA4",
+ "DIA5",
+ "DIA6",
+ "DIA7",
+ "DIA8",
+ "DIA9",
+ "DIA10",
+ "DIA11",
+ "DIA12",
+ "DIA13",
+ "DIA14",
+ "DIA15",
+ "DIA16",
+ "DIA17",
+ "DIB0",
+ "DIB1",
+ "DIB2",
+ "DIB3",
+ "DIB4",
+ "DIB5",
+ "DIB6",
+ "DIB7",
+ "DIB8",
+ "DIB9",
+ "DIB10",
+ "DIB11",
+ "DIB12",
+ "DIB13",
+ "DIB14",
+ "DIB15",
+ "DIB16",
+ "DIB17",
+ ]
+
+ output_pins: ClassVar[List[str]] = [
+ # DO
+ "DOA0",
+ "DOA1",
+ "DOA2",
+ "DOA3",
+ "DOA4",
+ "DOA5",
+ "DOA6",
+ "DOA7",
+ "DOA8",
+ "DOA9",
+ "DOA10",
+ "DOA11",
+ "DOA12",
+ "DOA13",
+ "DOA14",
+ "DOA15",
+ "DOA16",
+ "DOA17",
+ "DOB0",
+ "DOB1",
+ "DOB2",
+ "DOB3",
+ "DOB4",
+ "DOB5",
+ "DOB6",
+ "DOB7",
+ "DOB8",
+ "DOB9",
+ "DOB10",
+ "DOB11",
+ "DOB12",
+ "DOB13",
+ "DOB14",
+ "DOB15",
+ "DOB16",
+ "DOB17",
+ ]
+
+ @classmethod
+ def print_definition(cls) -> None:
+ """ Print the Verilog code for the module definition """
+ print(
+ f"""
+module ECP5_EBR(
+ input {", ".join(cls.input_pins)},
+ output {", ".join(cls.output_pins)}
+);
+
+ /* These defaults correspond to all-zero-bit enumeration values */
+ parameter CSDECODE_A = 3'b111;
+ parameter CSDECODE_B = 3'b111;
+ parameter ADA0MUX = "ADA0";
+ parameter ADA2MUX = "ADA2";
+ parameter ADA3MUX = "ADA3";
+ parameter ADB0MUX = "ADB0";
+ parameter ADB1MUX = "ADB1";
+ parameter CEAMUX = "CEA";
+ parameter CEBMUX = "CEB";
+ parameter CLKAMUX = "CLKA";
+ parameter CLKBMUX = "CLKB";
+ parameter DP16KD_DATA_WIDTH_A = "18";
+ parameter DP16KD_DATA_WIDTH_B = "18";
+ parameter DP16KD_WRITEMODE_A = "NORMAL";
+ parameter DP16KD_WRITEMODE_B = "NORMAL";
+ parameter MODE = "NONE";
+ parameter OCEAMUX = "OCEA";
+ parameter OCEBMUX = "OCEB";
+ parameter PDPW16KD_DATA_WIDTH_R = "18";
+ parameter PDPW16KD_RESETMODE = "SYNC";
+ parameter WEAMUX = "WEA";
+ parameter WEBMUX = "WEB";
+
+ /* TODO! */
+
+endmodule
+""".strip()
+ )
+
+ def print_instance(self, instname: str) -> None:
+ print("ECP5_EBR #(")
+ self._print_parameters({})
+ print(f") {instname} (")
+ self._print_pins()
+ print(");")
+ print()
+
+
+def print_verilog(graph: ConnectionGraph, tiles_by_loc: TilesByLoc, top_name: str) -> None:
+ # Extract connected components and their roots & leaves
+ sorted_components: List[Tuple[Component, List[Node], List[Node]]] = []
+ for component in graph.get_components():
+ roots = sorted([node for node in component.get_roots() if filter_node(node)])
+ if not roots:
+ continue
+ leaves = sorted([node for node in component.get_leaves() if filter_node(node)])
+ if not leaves:
+ continue
+ sorted_components.append((component, roots, leaves))
+ sorted_components = sorted(sorted_components, key=lambda x: x[1][0])
+
+ # Verilog input, output, and external wires
+ mod_sources: Set[Node] = set()
+ mod_sinks: Dict[Node, Node] = {}
+ mod_globals: Set[Node] = set()
+
+ modules: Dict[str, Module] = {}
+
+ print("/* Automatically generated by ecp_vlog.py")
+ for component, roots, leaves in sorted_components:
+ if len(roots) > 1:
+ print()
+ print("Unhandled multi-root component:")
+ print(*roots, sep=", ")
+ print(" -> ", end="")
+ print(*leaves, sep=", ")
+ continue
+
+ mod_sources.add(roots[0])
+ for node in leaves:
+ mod_sinks[node] = roots[0]
+ for node in roots + leaves:
+ if node.mod_name in modules:
+ modules[node.mod_name].pin_map[node.pin_name] = roots[0]
+ continue
+
+ mod_def = Module.create_from_node(node, tiles_by_loc)
+ if not mod_def:
+ mod_globals.add(node)
+ continue
+ mod_def.pin_map[node.pin_name] = roots[0]
+ modules[node.mod_name] = mod_def
+
+ # filter out any globals that are just copies of inputs or other outputs
+ for node in mod_globals:
+ if node in mod_sinks and mod_sinks[node] in mod_globals:
+ print(f"filtered out passed-through output: {mod_sinks[node]} -> {node}")
+ del mod_sinks[node]
+ all_sources: Set[Node] = set()
+ for sink in mod_sinks:
+ all_sources.add(mod_sinks[sink])
+ for node in mod_globals:
+ if node in mod_sources and node not in all_sources:
+ print(f"filtered out unused input: {node}")
+ mod_sources.discard(node)
+ print("*/")
+
+ for mod_type in set(type(mod_def) for mod_def in modules.values()):
+ mod_type.print_definition()
+
+ print(f"module {top_name}(")
+ mod_globals_vars = [" input wire " + str(node) for node in mod_sources & mod_globals]
+ mod_globals_vars += [" output wire " + str(node) for node in set(mod_sinks) & mod_globals]
+ print(" ,\n".join(natsorted(mod_globals_vars)))
+ print(");")
+ print()
+
+ # sources are either connected to global inputs
+ # or are outputs from some other node
+ for node in natsorted(mod_sources - mod_globals, key=str):
+ print(f"wire {node} ;")
+ print()
+
+ # sinks are either fed directly into a BEL,
+ # in which case they are directly substituted,
+ # or they are global outputs
+ for node in natsorted(set(mod_sinks) & mod_globals, key=str):
+ print(f"assign {node} = {mod_sinks[node]} ;")
+ print()
+
+ for modname in natsorted(modules):
+ modules[modname].print_instance(modname)
+
+ # debugging: print out any enums or words that we didn't handle in a Module
+ print("/* Unhandled enums/words:")
+ seen_enums: Set[Tuple[pytrellis.TileConfig, int]] = set()
+ seen_words: Set[Tuple[pytrellis.TileConfig, int]] = set()
+ for module in modules.values():
+ for i, e in enumerate(module.tiledata.cfg.cenums):
+ bel, _ = e.name.split(".", 1)
+ if bel == module.module_name:
+ seen_enums.add((module.tiledata.cfg, i))
+ for i, w in enumerate(module.tiledata.cfg.cwords):
+ bel, _ = w.name.split(".", 1)
+ if bel == module.module_name:
+ seen_words.add((module.tiledata.cfg, i))
+ for loc in sorted(tiles_by_loc.keys(), key=lambda loc: (loc[1], loc[0])):
+ for tiledata in tiles_by_loc[loc]:
+ for i, e in enumerate(tiledata.cfg.cenums):
+ if (tiledata.cfg, i) not in seen_enums:
+ print(" ", tiledata.tile.info.name, "enum:", e.name, e.value)
+ for i, w in enumerate(tiledata.cfg.cwords):
+ if (tiledata.cfg, i) not in seen_words:
+ valuestr = "".join([str(int(c)) for c in w.value][::-1])
+ print(" ", tiledata.tile.info.name, "word:", w.name, valuestr)
+ print("*/")
+ print("endmodule")
+
+
+def parse_lpf(filename: str) -> Dict[str, str]:
+ import shlex
+
+ lines = []
+ with open(filename, "r") as f:
+ for row in f:
+ row = row.split("#", 1)[0].split("//", 1)[0].strip()
+ if row:
+ lines.append(row)
+
+ sites: Dict[str, str] = {}
+
+ commands = " ".join(lines).split(";")
+ for cmd in commands:
+ cmd = cmd.strip()
+ if not cmd:
+ continue
+
+ words = shlex.split(cmd)
+ if words[0] == "LOCATE":
+ if len(words) != 5 or words[1] != "COMP" or words[3] != "SITE":
+ print("ignoring malformed LOCATE in LPF:", cmd, file=sys.stderr)
+ sites[words[4]] = words[2]
+
+ return sites
+
+
+def main(argv: List[str]) -> None:
+ import argparse
+ import json
+
+ parser = argparse.ArgumentParser("Convert a .bit file into a .v verilog file for simulation")
+
+ parser.add_argument("bitfile", help="Input .bit file")
+ parser.add_argument("--package", help="Physical package (e.g. CABGA256), for renaming I/O ports")
+ parser.add_argument("--lpf", help="Use LOCATE COMP commands from this LPF file to name I/O ports")
+ parser.add_argument("-n", "--module-name", help="Name for the top-level module (default: top)", default="top")
+ args = parser.parse_args(argv)
+
+ if args.lpf and not args.package:
+ parser.error("Cannot use a LPF file without specifying the chip package")
+
+ pytrellis.load_database(database.get_db_root())
+
+ print("Loading bitstream...", file=sys.stderr)
+ bitstream = pytrellis.Bitstream.read_bit(args.bitfile)
+ chip = bitstream.deserialise_chip()
+
+ if args.package:
+ dbfn = os.path.join(database.get_db_subdir(chip.info.family, chip.info.name), "iodb.json")
+ with open(dbfn, "r") as f:
+ iodb = json.load(f)
+
+ if args.lpf:
+ lpf_map = parse_lpf(args.lpf)
+ else:
+ lpf_map = {}
+
+ # Rename PIO and IOLOGIC BELs based on their connected pins, for readability
+ mod_renames = {}
+ for pin_name, pin_data in iodb["packages"][args.package].items():
+ if pin_name in lpf_map:
+ # escape LPF name in case it has funny characters
+ pin_name = "\\" + lpf_map[pin_name]
+ # PIO and IOLOGIC do not share pin names except for IOLDO/IOLTO
+ mod_renames["R{row}C{col}_PIO{pio}".format(**pin_data)] = f"{pin_name}"
+ mod_renames["R{row}C{col}_IOLOGIC{pio}".format(**pin_data)] = f"{pin_name}"
+
+ # Note: the mod_name_map only affects str(node), not node.mod_name
+ Node.mod_name_map = mod_renames
+
+ print("Computing routing graph...", file=sys.stderr)
+ rgraph = chip.get_routing_graph()
+
+ print("Computing connection graph...", file=sys.stderr)
+ tiles_by_loc = make_tiles_by_loc(chip)
+ graph = gen_config_graph(chip, rgraph, tiles_by_loc)
+
+ print("Generating Verilog...", file=sys.stderr)
+ print_verilog(graph, tiles_by_loc, args.module_name)
+
+ print("Done!", file=sys.stderr)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])