fix(cot-dt2c): fix various breakages

This change fixes several breakages that were introduced in some build
configurations by the introduction of the cot-dt2c tool.

Some Python environments cannot be managed directly via `pip`, and
invocations of `make`, including `make distclean`, would cause errors
along the lines of:

    error: externally-managed-environment

    × This environment is externally managed
    ╰─> To install Python packages system-wide, try apt install
        python3-xyz, where xyz is the package you are trying to
        install.

This change has been resolved by ensuring that calls to the cot-dt2c
tool from the build system happen exclusively through Poetry, which
automatically sets up a virtual environment that *can* be modified.

Some environments saw the following error when building platforms where
the cot-dt2c tool was used:

    make: *** No rule to make target '<..>/debug/bl2_cot.c', needed
    by '<..>/debug/bl2/bl2_cot.o'.  Stop.

Additionally, environments with a more recent version of Python saw the
following error:

      File "<...>/lib/python3.12/site-packages/cot_dt2c/cot_parser.py",
      line 637, in img_to_c
        if ifdef:
           ^^^^^
    NameError: name 'ifdef' is not defined

Both of these errors have now been resolved by modifications to the
build system and the cot-dt2c tool to enable preprocessing of the device
tree source file before it is processed by the tool.

As a consequence of this change, the `pydevicetree` library is no longer
vendored into the repository tree, and we instead pull it in via a
dependency in Poetry.

This change also resolves several MyPy warnings and errors related to
missing type hints.

Change-Id: I72b2d01caca3fcb789d3fe2549f318a9c92d77d1
Signed-off-by: Chris Kay <chris.kay@arm.com>
diff --git a/Makefile b/Makefile
index 64bccbc..cb88758 100644
--- a/Makefile
+++ b/Makefile
@@ -121,9 +121,6 @@
 SP_MK_GEN		?=	${SPTOOLPATH}/sp_mk_generator.py
 SP_DTS_LIST_FRAGMENT	?=	${BUILD_PLAT}/sp_list_fragment.dts
 
-# Variables for use with Certificate Conversion (cot-dt2c) Tool
-CERTCONVPATH	?= tools/cot_dt2c
-
 # Variables for use with ROMLIB
 ROMLIBPATH		?=	lib/romlib
 
@@ -1619,7 +1616,6 @@
 clean:
 	$(s)echo "  CLEAN"
 	$(call SHELL_REMOVE_DIR,${BUILD_PLAT})
-	$(q)${MAKE} -C ${CERTCONVPATH} clean
 ifdef UNIX_MK
 	$(q)${MAKE} --no-print-directory -C ${FIPTOOLPATH} clean
 else
@@ -1635,7 +1631,6 @@
 	$(s)echo "  REALCLEAN"
 	$(call SHELL_REMOVE_DIR,${BUILD_BASE})
 	$(call SHELL_DELETE_ALL, ${CURDIR}/cscope.*)
-	$(q)${MAKE} -C ${CERTCONVPATH} clean
 ifdef UNIX_MK
 	$(q)${MAKE} --no-print-directory -C ${FIPTOOLPATH} clean
 else
diff --git a/plat/arm/common/arm_common.mk b/plat/arm/common/arm_common.mk
index dff0135..3c4ad64 100644
--- a/plat/arm/common/arm_common.mk
+++ b/plat/arm/common/arm_common.mk
@@ -406,7 +406,6 @@
           COTDTPATH := fdts/tbbr_cot_descriptors.dtsi
         endif
       endif
-      bl2: cot-dt2c
     endif
 
     BL1_SOURCES		+=	${AUTH_SOURCES}					\
@@ -481,16 +480,23 @@
   endif
 endif
 
-cot-dt2c:
 ifneq ($(COTDTPATH),)
-  $(info COT CONVERSION FOR ${COTDTPATH})
-  toolpath := $(shell which cot-dt2c)
-  ifeq (${toolpath},)
-    output := $(shell make -C ./${CERTCONVPATH} install)
-    $(info install output ${output})
-    toolpath := $(shell which cot-dt2c)
-  endif
-  output := $(shell ${toolpath} convert-to-c ${COTDTPATH} ${BUILD_PLAT}/bl2_cot.c)
-  $(info ${output})
-  BL2_SOURCES += ${BUILD_PLAT}/bl2_cot.c
+        cot-dt-defines = IMAGE_BL2 $(BL2_DEFINES) $(PLAT_BL_COMMON_DEFINES)
+        cot-dt-include-dirs = $(BL2_INCLUDE_DIRS) $(PLAT_BL_COMMON_INCLUDE_DIRS)
+
+        cot-dt-cpp-flags  = $(cot-dt-defines:%=-D%)
+        cot-dt-cpp-flags += $(cot-dt-include-dirs:%=-I%)
+
+        cot-dt-cpp-flags += $(BL2_CPPFLAGS) $(PLAT_BL_COMMON_CPPFLAGS)
+        cot-dt-cpp-flags += $(CPPFLAGS) $(BL_CPPFLAGS) $(TF_CFLAGS_$(ARCH))
+        cot-dt-cpp-flags += -c -x assembler-with-cpp -E -P -o $@ $<
+
+        $(BUILD_PLAT)/$(COTDTPATH:.dtsi=.dts): $(COTDTPATH) | $$(@D)/
+		$(q)$($(ARCH)-cpp) $(cot-dt-cpp-flags)
+
+        $(BUILD_PLAT)/$(COTDTPATH:.dtsi=.c): $(BUILD_PLAT)/$(COTDTPATH:.dtsi=.dts) | $$(@D)/
+		$(q)poetry -q install
+		$(q)poetry run cot-dt2c convert-to-c $< $@
+
+        BL2_SOURCES += $(BUILD_PLAT)/$(COTDTPATH:.dtsi=.c)
 endif
diff --git a/poetry.lock b/poetry.lock
index b465f48..9b98b18 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -217,6 +217,26 @@
 test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
 
 [[package]]
+name = "cot-dt2c"
+version = "0.1.0"
+description = "CoT-dt2c Tool is a python script to convert CoT DT file into corresponding C file"
+optional = false
+python-versions = "^3.8"
+files = []
+develop = true
+
+[package.dependencies]
+click = "^8.1.7"
+igraph = "^0.11.6"
+plotly = "^5.23.0"
+pydevicetree = "0.0.13"
+pyparsing = "^3.1.2"
+
+[package.source]
+type = "directory"
+url = "tools/cot_dt2c"
+
+[[package]]
 name = "docutils"
 version = "0.18.1"
 description = "Docutils -- Python Documentation Utilities"
@@ -239,6 +259,66 @@
 ]
 
 [[package]]
+name = "igraph"
+version = "0.11.6"
+description = "High performance graph data structures and algorithms"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "igraph-0.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3f8b837181e8e87676be3873ce87cc92cc234efd58a2da2f6b4e050db150fcf4"},
+    {file = "igraph-0.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:245c4b7d7657849eff80416f5df4525c8fc44c74a981ee4d44f0ef2612c3bada"},
+    {file = "igraph-0.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdb7be3d165073c0136295c0808e9edc57ba096cdb26e94086abb04561f7a292"},
+    {file = "igraph-0.11.6-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58974e20df2986a1ae52a16e51ecb387cc0cbeb41c5c0ddff4d373a1bbf1d9c5"},
+    {file = "igraph-0.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bef14de5e8ab70724a43808b1ed14aaa6fe1002f87e592289027a3827a8f44a"},
+    {file = "igraph-0.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:86c1e98de2e32d074df8510bf18abfa1f4c5fda4cb28a009985a5d746b0c0125"},
+    {file = "igraph-0.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ebc5b3d702158abeb2e4d2414374586a2b932e1a07e48352b470600e1733d528"},
+    {file = "igraph-0.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0efe6d0fb22d3987a800eb3857ed04df9eb4c5dddd0998be05232cb646f1c337"},
+    {file = "igraph-0.11.6-cp38-cp38-win32.whl", hash = "sha256:f4e68b27497b1c8ada2fb2bc35ef3fa7b0d72e84306b3d648d3de240fc618c32"},
+    {file = "igraph-0.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:5665b33dfbfca5f54ce9b4fea6b97903bd0e99fb1b02acf5e57e600bdfa5a355"},
+    {file = "igraph-0.11.6-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8aabef03d787b519d1075dfc0da4a1109fb113b941334883e3e7947ac30a459e"},
+    {file = "igraph-0.11.6-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1f2cc4a518d99cdf6cae514f85e93e56852bc8c325b3abb96037d1d690b5975f"},
+    {file = "igraph-0.11.6-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e859238be52ab8ccc614d18f9362942bc88ce543afc12548f81ae99b10801d"},
+    {file = "igraph-0.11.6-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d61fbe5e85eb4ae9efe08c461f9bdeedb02a2b5739fbc223d324a71f40a28be2"},
+    {file = "igraph-0.11.6-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6620ba39df29fd42151becf82309b54e57148233c9c3ef890eed62e25eed8a5"},
+    {file = "igraph-0.11.6-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59666589bb3d07f310cda2c5106a8adeeb77c2ef27fecf1c6438b6091f4ca69d"},
+    {file = "igraph-0.11.6-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:8750b6d6caebf199cf7dc41c931f58e330153779707391e30f0a29f02666fb6e"},
+    {file = "igraph-0.11.6-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:967d6f2c30fe94317da15e459374d0fb8ca3e56020412f201ecd07dd5b5352f2"},
+    {file = "igraph-0.11.6-cp39-abi3-win32.whl", hash = "sha256:9744f95a67319eb6cb487ceabf30f5d7940de34bada51f0ba63adbd23e0f94ad"},
+    {file = "igraph-0.11.6-cp39-abi3-win_amd64.whl", hash = "sha256:b80e69eb11faa9c57330a9ffebdde5808966efe1c1f638d4d4827ea04df7aca8"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0329c16092e2ea7930d5f8368666ce7cb704900cc0ea04e4afe9ea1dd46e44af"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21752313f449bd8688e5688e95ea7231cea5e9199c7162535029be0d9af848ac"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea25e136c6c4161f53ff58868b23ff6c845193050ab0e502236d68e5d4174e32"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac84433a03aef15e4b810010b08882b09854a3669450ccf31e392dbe295d2a66"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac697a44e3573169fa2b28c9c37dcf9cf01e0f558b845dd7123860d4c7c8fb89"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bdeae8bf35316eb1fb27bf667dcf5ecf5fcfb0b8f51831bc1b00c39c09c2d73b"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ad7e4aa442935de72554b96733bf6d7f09eac5cee97988a2562bdd3ca173cfa3"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:8d2818780358a686178866d01568b9df1f29678581734ad7a78882bab54df004"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2352276a20d979f1dea360af4202bb9f0c9a7d2c77f51815c0e625165e82013d"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:687fdab543b507d622fa3043f4227e5b26dc61dcf8ff8c0919fccddcc655f8b8"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57f7f8214cd48c9a4d97f7346a4152ba2d4ac95fb5ee0df4ecf224fce4ba3d14"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2b9cc69ede53f76ffae03b066609aa90184dd68ef15da8c104a97cebb9210838"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:591e1e447c3f0092daf7613a3eaedab83f9a0b0adbaf7702724c5117ded038a5"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ca558eb331bc687bc33e5cd23717e22676e9412f8cda3a31d30c996a0487610d"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf43c30e08debb087c9e3da69aa5cf1b6732968da34d55a614e3421b9a452146"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d38e8d7db72b187d9d2211d0d06b3271fa9f32b04d49d789e2859b5480db0d0"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a318b059051ff78144a1c3cb880f4d933c812bcdb3d833a49cd7168d0427672"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c54027add809b3c5b6685b8deca4ea4763fd000b9ea45c7ee46b7c9d61ff15e"},
+    {file = "igraph-0.11.6.tar.gz", hash = "sha256:837f233256c3319f2a35a6a80d94eafe47b43791ef4c6f9e9871061341ac8e28"},
+]
+
+[package.dependencies]
+texttable = ">=1.6.2"
+
+[package.extras]
+cairo = ["cairocffi (>=1.2.0)"]
+doc = ["Sphinx (>=7.0.0)", "pydoctor (>=23.4.0)", "sphinx-gallery (>=0.14.0)", "sphinx-rtd-theme (>=1.3.0)"]
+matplotlib = ["matplotlib (>=3.6.0)"]
+plotly = ["plotly (>=5.3.0)"]
+plotting = ["cairocffi (>=1.2.0)"]
+test = ["Pillow (>=9)", "cairocffi (>=1.2.0)", "matplotlib (>=3.6.0)", "networkx (>=2.5)", "numpy (>=1.19.0)", "pandas (>=1.1.0)", "plotly (>=5.3.0)", "pytest (>=7.0.1)", "pytest-timeout (>=2.1.0)", "scipy (>=1.5.0)"]
+test-musl = ["cairocffi (>=1.2.0)", "networkx (>=2.5)", "pytest (>=7.0.1)", "pytest-timeout (>=2.1.0)"]
+
+[[package]]
 name = "imagesize"
 version = "1.4.1"
 description = "Getting image size from png/jpeg/jpeg2000/gif file"
@@ -480,6 +560,21 @@
 testing = ["flit-core (>=2,<4)", "poetry-core (>=1.0.0)", "pytest (>=7.2.0)", "pytest-rerunfailures", "pytest-xdist", "tomli-w"]
 
 [[package]]
+name = "plotly"
+version = "5.23.0"
+description = "An open-source, interactive data visualization library for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "plotly-5.23.0-py3-none-any.whl", hash = "sha256:76cbe78f75eddc10c56f5a4ee3e7ccaade7c0a57465546f02098c0caed6c2d1a"},
+    {file = "plotly-5.23.0.tar.gz", hash = "sha256:89e57d003a116303a34de6700862391367dd564222ab71f8531df70279fc0193"},
+]
+
+[package.dependencies]
+packaging = "*"
+tenacity = ">=6.2.0"
+
+[[package]]
 name = "prettytable"
 version = "3.10.2"
 description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format"
@@ -497,6 +592,20 @@
 tests = ["pytest", "pytest-cov", "pytest-lazy-fixtures"]
 
 [[package]]
+name = "pydevicetree"
+version = "0.0.13"
+description = "A library for parsing Devicetree Source v1"
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "pydevicetree-0.0.13-py3-none-any.whl", hash = "sha256:d61c695cec925b90a8b5740053f4b604e51154a9b36e62a2f12ed9ceaf2f8c38"},
+    {file = "pydevicetree-0.0.13.tar.gz", hash = "sha256:5700c05df89bad8fd729c11aa6f764a3323bcb3796f13b32481ae34445cfc1b7"},
+]
+
+[package.dependencies]
+pyparsing = "*"
+
+[[package]]
 name = "pyelftools"
 version = "0.29"
 description = "Library for analyzing ELF files and DWARF debugging information"
@@ -522,6 +631,20 @@
 windows-terminal = ["colorama (>=0.4.6)"]
 
 [[package]]
+name = "pyparsing"
+version = "3.1.2"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+optional = false
+python-versions = ">=3.6.8"
+files = [
+    {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"},
+    {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"},
+]
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
 name = "pyproject-hooks"
 version = "1.1.0"
 description = "Wrappers to call pyproject.toml-based build backend hooks."
@@ -883,6 +1006,32 @@
 cairosvg = ["cairosvg (>=1.0)"]
 
 [[package]]
+name = "tenacity"
+version = "9.0.0"
+description = "Retry code until it succeeds"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"},
+    {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"},
+]
+
+[package.extras]
+doc = ["reno", "sphinx"]
+test = ["pytest", "tornado (>=4.5)", "typeguard"]
+
+[[package]]
+name = "texttable"
+version = "1.7.0"
+description = "module to create simple ASCII tables"
+optional = false
+python-versions = "*"
+files = [
+    {file = "texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917"},
+    {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"},
+]
+
+[[package]]
 name = "tlc"
 version = "0.9.0"
 description = "Transfer List Compiler (TLC) is a Python-based CLI for efficiently handling transfer lists."
@@ -1005,4 +1154,4 @@
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.8"
-content-hash = "8798a2d1efd456c3b68ae464a216f015afaa1bbb653a905148bef17ab8ce278e"
+content-hash = "d893034cad02533bc86fb98c7d93a0eac6a755fea5efd553924e4762ed3f1fdb"
diff --git a/pyproject.toml b/pyproject.toml
index 03d898e..f0b3925 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,6 +14,7 @@
 
 [tool.poetry.dependencies]
 python = "^3.8"
+cot-dt2c = {path = "tools/cot_dt2c", develop = true}
 tlc = {path = "tools/tlc"}
 
 [tool.poetry.group.docs]
diff --git a/tools/cot_dt2c/.gitignore b/tools/cot_dt2c/.gitignore
new file mode 100644
index 0000000..ad4a1f1
--- /dev/null
+++ b/tools/cot_dt2c/.gitignore
@@ -0,0 +1,176 @@
+# Created by https://www.toptal.com/developers/gitignore/api/python
+# Edit at https://www.toptal.com/developers/gitignore?templates=python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
+
+# End of https://www.toptal.com/developers/gitignore/api/python
diff --git a/tools/cot_dt2c/Makefile b/tools/cot_dt2c/Makefile
deleted file mode 100644
index ad8d9f5..0000000
--- a/tools/cot_dt2c/Makefile
+++ /dev/null
@@ -1,68 +0,0 @@
-#
-# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-##* Variables
-SHELL := /usr/bin/env bash
-PYTHON := python
-PYTHONPATH := `pwd`
-
-.PHONY: dist
-dist: clean
-	poetry build
-
-#* Installation
-.PHONY: dev-install
-dev-install:
-	pip3 install mypy
-	pip3 install pytest
-	pip install -r requirements.txt
-	poetry lock -n && poetry export --without-hashes > requirements.txt
-	poetry install -n
-	-poetry run mypy --install-types --non-interactive ./
-
-.PHONY: install
-install: dist
-	pip install mypy
-	pip install pytest
-	pip install -r requirements.txt
-	pip install dist/*.whl
-
-clean-test: ## remove test and coverage artifacts
-	rm -fr .tox/
-	rm -f .coverage
-	rm -fr htmlcov/
-
-clean-pyc: ## remove Python file artifacts
-	find . -name '*.pyc' -exec rm -f {} +
-	find . -name '*.pyo' -exec rm -f {} +
-	find . -name '*~' -exec rm -f {} +
-	find . -name '__pycache__' -exec rm -fr {} +
-	find . | grep -E ".pytest_cache" | xargs rm -rf
-	find . | grep -E ".mypy_cache" | xargs rm -rf
-
-
-clean-build: ## remove build artifacts
-	rm -fr build/
-	rm -fr dist/
-	rm -fr .eggs/
-	find . -name '*.egg-info' -exec rm -fr {} +
-	find . -name '*.egg' -exec rm -f {} +
-
-clean-tmp:
-	rm -rf ./tmp
-
-#* Cleaning
-.PHONY: clean clean-build clean-pyc clean-test
-clean: uninstall clean-build clean-pyc clean-test clean-tmp ## remove all build, test, coverage and Python artifacts
-
-uninstall:
-	pip uninstall -y cot-dt2c
-
-.PHONY: reinstall
-reinstall: clean install
-
-.PHONY: test
-test:
-	PYTHONPATH=$(PYTHONPATH) poetry run pytest -c pyproject.toml tests/
diff --git a/tools/cot_dt2c/cot_dt2c/LICENSE b/tools/cot_dt2c/cot_dt2c/LICENSE
deleted file mode 100644
index d645695..0000000
--- a/tools/cot_dt2c/cot_dt2c/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   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
-
-       http://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.
diff --git a/tools/cot_dt2c/cot_dt2c/cot_parser.py b/tools/cot_dt2c/cot_dt2c/cot_parser.py
index c1d53e2..39e51db 100644
--- a/tools/cot_dt2c/cot_dt2c/cot_parser.py
+++ b/tools/cot_dt2c/cot_dt2c/cot_parser.py
@@ -6,35 +6,15 @@
 
 import sys
 import re
-from cot_dt2c.pydevicetree.source.parser import ifdef_stack
-from cot_dt2c.pydevicetree.ast import CellArray, LabelReference
-from cot_dt2c.pydevicetree import *
+from pydevicetree.ast import CellArray, LabelReference
+from pydevicetree import Devicetree, Property, Node
 from pathlib import Path
-
-def extractNumber(s):
-    for i in s:
-        if i.isdigit():
-            return (int)(i)
-
-    return -1
-
-def removeNumber(s):
-    result = ''.join([i for i in s if not i.isdigit()])
-    return result
+from typing import List, Optional
 
 class COT:
     def __init__(self, inputfile: str, outputfile=None):
-        with open(inputfile, 'r') as f:
-            contents = f.read()
-        pos = contents.find("cot")
-        if pos == -1:
-            print("not a valid CoT DT file")
-            exit(1)
-
-        contents = contents[pos:]
-
         try:
-            self.tree = Devicetree.parseStr(contents)
+            self.tree = Devicetree.parseFile(inputfile)
         except:
             print("not a valid CoT DT file")
             exit(1)
@@ -336,13 +316,10 @@
 
     def format_auth_data_val(self, node:Node, cert:Node):
         type_desc = node.name
-        if "sp_pkg" in type_desc:
-            ptr = removeNumber(type_desc) + "_buf"
-        else:
-            ptr = type_desc + "_buf"
-        len = "(unsigned int)HASH_DER_LEN"
-        if "pk" in type_desc:
-            len = "(unsigned int)PK_DER_LEN"
+        ptr = type_desc + "_buf"
+        len = "HASH_DER_LEN"
+        if re.search("_pk$", type_desc):
+            len = "PK_DER_LEN"
 
         # edge case
         if not self.if_root(cert) and "key_cert" in cert.name:
@@ -351,7 +328,7 @@
 
         return type_desc, ptr, len
 
-    def get_node(self, nodes: list[Node], name: str) -> Node:
+    def get_node(self, nodes: List[Node], name: str) -> Node:
         for i in nodes:
             if i.name == name:
                 return i
@@ -458,10 +435,6 @@
     def validate_nodes(self) -> bool:
         valid = True
 
-        if ifdef_stack:
-            print("invalid ifdef macro")
-            valid = False
-
         certs = self.get_all_certificates()
         images = self.get_all_images()
 
@@ -478,71 +451,16 @@
 
         return valid
 
-    def extract_licence(self, f):
-        licence = []
-
-        licencereg = re.compile(r'/\*')
-        licenceendReg = re.compile(r'\*/')
-
-        licencePre = False
-
-        for line in f:
-            match = licencereg.search(line)
-            if match != None:
-                licence.append(line)
-                licencePre = True
-                continue
-
-            match = licenceendReg.search(line)
-            if match != None:
-                licence.append(line)
-                licencePre = False
-                return licence
-
-            if licencePre:
-                licence.append(line)
-            else:
-                return licence
-
-        return licence
-
-    def licence_to_c(self, licence, f):
-        if len(licence) != 0:
-            for i in licence:
-                f.write(i)
-
-        f.write("\n")
-        return
-
-    def extract_include(self, f):
-        include = []
-
-        for line in f:
-            if "cot" in line:
-                return include
-
-            if line != "" and "common" not in line and line != "\n":
-                include.append(line)
-
-        return include
-
-    def include_to_c(self, include, f):
+    def include_to_c(self, f):
         f.write("#include <stddef.h>\n")
         f.write("#include <mbedtls/version.h>\n")
         f.write("#include <common/tbbr/cot_def.h>\n")
         f.write("#include <drivers/auth/auth_mod.h>\n")
-        f.write("\n")
-        for i in include:
-            f.write(i)
-        f.write("\n")
         f.write("#include <platform_def.h>\n\n")
         return
 
-    def generate_header(self, input, output):
-        licence = self.extract_licence(input)
-        include = self.extract_include(input)
-        self.licence_to_c(licence, output)
-        self.include_to_c(include, output)
+    def generate_header(self, output):
+        self.include_to_c(output)
 
     def all_cert_to_c(self, f):
         certs = self.get_all_certificates()
@@ -552,17 +470,16 @@
         f.write("\n")
 
     def cert_to_c(self, node: Node, f):
-        ifdef = node.get_fields("ifdef")
-        if ifdef:
-            for i in ifdef:
-                f.write("{}\n".format(i))
+        node_image_id: int = node.get_field("image-id")
 
-        f.write("static const auth_img_desc_t {} = {{\n".format(node.name))
-        f.write("\t.img_id = {},\n".format(node.get_field("image-id").values[0].replace('"', "")))
+        f.write(f"static const auth_img_desc_t {node.name} = {{\n")
+        f.write(f"\t.img_id = {node_image_id},\n")
         f.write("\t.img_type = IMG_CERT,\n")
 
         if not self.if_root(node):
-            f.write("\t.parent = &{},\n".format(node.get_field("parent").label.name))
+            node_parent: Node = node.get_field("parent")
+
+            f.write(f"\t.parent = &{node_parent.label.name},\n")
         else:
             f.write("\t.parent = NULL,\n")
 
@@ -608,13 +525,9 @@
                 f.write("\t\t\t.type_desc = &{},\n".format(type_desc))
                 f.write("\t\t\t.data = {\n")
 
-                n = extractNumber(type_desc)
-                if "pkg" not in type_desc or n == -1:
-                    f.write("\t\t\t\t.ptr = (void *){},\n".format(ptr))
-                else:
-                    f.write("\t\t\t\t.ptr = (void *){}[{}],\n".format(ptr, n-1))
+                f.write("\t\t\t\t.ptr = (void *){},\n".format(ptr))
 
-                f.write("\t\t\t\t.len = {}\n".format(data_len))
+                f.write("\t\t\t\t.len = (unsigned int){}\n".format(data_len))
                 f.write("\t\t\t}\n")
 
                 f.write("\t\t}}{}\n".format("," if i != len(auth_data) - 1 else ""))
@@ -623,42 +536,31 @@
 
         f.write("};\n\n")
 
-        if ifdef:
-            for i in ifdef:
-                f.write("#endif\n")
-            f.write("\n")
-
         return
 
 
     def img_to_c(self, node:Node, f):
-        ifdef = node.get_fields("ifdef")
-        if ifdef:
-            for i in ifdef:
-                f.write("{}\n".format(i))
+        node_image_id: int = node.get_field("image-id")
+        node_parent: Node = node.get_field("parent")
+        node_hash: Node = node.get_field("hash")
 
-        f.write("static const auth_img_desc_t {} = {{\n".format(node.name))
-        f.write("\t.img_id = {},\n".format(node.get_field("image-id").values[0].replace('"', "")))
+        f.write(f"static const auth_img_desc_t {node.name} = {{\n")
+        f.write(f"\t.img_id = {node_image_id},\n")
         f.write("\t.img_type = IMG_RAW,\n")
-        f.write("\t.parent = &{},\n".format(node.get_field("parent").label.name))
+        f.write(f"\t.parent = &{node_parent.label.name},\n")
         f.write("\t.img_auth_methods = (const auth_method_desc_t[AUTH_METHOD_NUM]) {\n")
 
         f.write("\t\t[0] = {\n")
         f.write("\t\t\t.type = AUTH_METHOD_HASH,\n")
         f.write("\t\t\t.param.hash = {\n")
         f.write("\t\t\t\t.data = &raw_data,\n")
-        f.write("\t\t\t\t.hash = &{}\n".format(node.get_field("hash").label.name))
+        f.write(f"\t\t\t\t.hash = &{node_hash.label.name}\n")
         f.write("\t\t\t}\n")
 
         f.write("\t\t}\n")
         f.write("\t}\n")
         f.write("};\n\n")
 
-        if ifdef:
-            for i in ifdef:
-                f.write("#endif\n")
-            f.write("\n")
-
         return
 
     def all_img_to_c(self, f):
@@ -672,7 +574,10 @@
         nv_ctr = self.get_all_nv_counters()
 
         for nv in nv_ctr:
-            f.write("static auth_param_type_desc_t {} = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_NV_CTR, {});\n".format(nv.name, nv.get_field("oid")))
+            nv_oid: str = nv.get_field("oid")
+
+            f.write(f"static auth_param_type_desc_t {nv.name} = "\
+                    f"AUTH_PARAM_TYPE_DESC(AUTH_PARAM_NV_CTR, \"{nv_oid}\");\n")
 
         f.write("\n")
 
@@ -682,7 +587,10 @@
         pks = self.get_all_pks()
 
         for p in pks:
-            f.write("static auth_param_type_desc_t {} = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_PUB_KEY, {});\n".format(p.name, p.get_field("oid")))
+            pk_oid: str = p.get_field("oid")
+
+            f.write(f"static auth_param_type_desc_t {p.name} = "\
+                    f"AUTH_PARAM_TYPE_DESC(AUTH_PARAM_PUB_KEY, \"{pk_oid}\");\n")
 
         f.write("\n")
         return
@@ -690,30 +598,17 @@
     def buf_to_c(self, f):
         certs = self.get_all_certificates()
 
-        buffers = {}
+        buffers = set()
 
         for c in certs:
             auth_data = self.get_auth_data(c)
+
             for a in auth_data:
                 type_desc, ptr, data_len = self.format_auth_data_val(a, c)
-                if ptr not in buffers:
-                    buffers[ptr] = c.get_fields("ifdef")
-
-        for key, values in buffers.items():
-            if values:
-                for i in values:
-                    f.write("{}\n".format(i))
 
-            if "sp_pkg_hash_buf" in key:
-                f.write("static unsigned char {}[MAX_SP_IDS][HASH_DER_LEN];\n".format(key))
-            elif "pk" in key:
-                f.write("static unsigned char {}[PK_DER_LEN];\n".format(key))
-            else:
-                f.write("static unsigned char {}[HASH_DER_LEN];\n".format(key))
-
-            if values:
-                for i in values:
-                    f.write("#endif\n")
+                if not ptr in buffers:
+                    f.write(f"static unsigned char {ptr}[{data_len}];\n")
+                    buffers.add(ptr)
 
         f.write("\n")
 
@@ -726,29 +621,18 @@
 
         certs = self.get_all_certificates()
         for c in certs:
-            ifdef = c.get_fields("ifdef")
-            if ifdef:
-                for i in ifdef:
-                    f.write("{}\n".format(i))
-
             hash = c.children
             for h in hash:
                 name = h.name
                 oid = h.get_field("oid")
 
-                if "pk" in name and "pkg" not in name:
-                    f.write("static auth_param_type_desc_t {} = "\
-                        "AUTH_PARAM_TYPE_DESC(AUTH_PARAM_PUB_KEY, {});\n".format(name, oid))
-                elif "hash" in name:
-                    f.write("static auth_param_type_desc_t {} = "\
-                            "AUTH_PARAM_TYPE_DESC(AUTH_PARAM_HASH, {});\n".format(name, oid))
-                elif "ctr" in name:
-                    f.write("static auth_param_type_desc_t {} = "\
-                            "AUTH_PARAM_TYPE_DESC(AUTH_PARAM_NV_CTR, {});\n".format(name, oid))
+                if re.search("_pk$", name):
+                    ty = "AUTH_PARAM_PUB_KEY"
+                elif re.search("_hash$", name):
+                    ty = "AUTH_PARAM_HASH"
 
-            if ifdef:
-                for i in ifdef:
-                    f.write("#endif\n")
+                f.write(f"static auth_param_type_desc_t {name} = "\
+                    f"AUTH_PARAM_TYPE_DESC({ty}, \"{oid}\");\n")
 
         f.write("\n")
 
@@ -759,28 +643,14 @@
         f.write("static const auth_img_desc_t * const cot_desc[] = {\n")
 
         for i, c in enumerate(certs):
-            ifdef = c.get_fields("ifdef")
-            if ifdef:
-                for i in ifdef:
-                    f.write("{}\n".format(i))
+            c_image_id: int = c.get_field("image-id")
 
-            f.write("\t[{}]	=	&{}{}\n".format(c.get_field("image-id").values[0], c.name, ","))
-
-            if ifdef:
-                for i in ifdef:
-                    f.write("#endif\n")
+            f.write(f"\t[{c_image_id}]	=	&{c.name},\n")
 
         for i, c in enumerate(images):
-            ifdef = c.get_fields("ifdef")
-            if ifdef:
-                for i in ifdef:
-                    f.write("{}\n".format(i))
+            c_image_id: int = c.get_field("image-id")
 
-            f.write("\t[{}]	=	&{}{}\n".format(c.get_field("image-id").values[0], c.name, "," if i != len(images) - 1 else ""))
-
-            if ifdef:
-                for i in ifdef:
-                    f.write("#endif\n")
+            f.write(f"\t[{c_image_id}]	=	&{c.name},\n")
 
         f.write("};\n\n")
         f.write("REGISTER_COT(cot_desc);\n")
@@ -789,16 +659,15 @@
     def generate_c_file(self):
         filename = Path(self.output)
         filename.parent.mkdir(exist_ok=True, parents=True)
-        output = open(self.output, 'w+')
-        input = open(self.input, "r")
 
-        self.generate_header(input, output)
-        self.buf_to_c(output)
-        self.param_to_c(output)
-        self.nv_to_c(output)
-        self.pk_to_c(output)
-        self.all_cert_to_c(output)
-        self.all_img_to_c(output)
-        self.cot_to_c(output)
+        with open(self.output, 'w+') as output:
+            self.generate_header(output)
+            self.buf_to_c(output)
+            self.param_to_c(output)
+            self.nv_to_c(output)
+            self.pk_to_c(output)
+            self.all_cert_to_c(output)
+            self.all_img_to_c(output)
+            self.cot_to_c(output)
 
         return
diff --git a/tools/cot_dt2c/cot_dt2c/dt_validator.py b/tools/cot_dt2c/cot_dt2c/dt_validator.py
index 65e8ca2..ade037c 100644
--- a/tools/cot_dt2c/cot_dt2c/dt_validator.py
+++ b/tools/cot_dt2c/cot_dt2c/dt_validator.py
@@ -7,7 +7,7 @@
 import sys
 from os import path, walk, mkdir
 import subprocess
-from cot_dt2c.pydevicetree import *
+from pydevicetree import Devicetree
 
 class bcolors:
     HEADER = '\033[95m'
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/__init__.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/__init__.py
deleted file mode 100644
index 49595a7..0000000
--- a/tools/cot_dt2c/cot_dt2c/pydevicetree/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2019 SiFive Inc.
-# SPDX-License-Identifier: Apache-2.0
-
-from cot_dt2c.pydevicetree.ast import Devicetree, Node, Property, Directive, CellArray, LabelReference
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/__init__.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/__init__.py
deleted file mode 100644
index f30d897..0000000
--- a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/__init__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2019 SiFive Inc.
-# SPDX-License-Identifier: Apache-2.0
-
-from cot_dt2c.pydevicetree.ast.directive import Directive
-from cot_dt2c.pydevicetree.ast.node import Node, NodeReference, Devicetree
-from cot_dt2c.pydevicetree.ast.property import PropertyValues, Bytestring, CellArray, StringList, Property, \
-                                      RegArray, OneString
-from cot_dt2c.pydevicetree.ast.reference import Label, Path, Reference, LabelReference, PathReference
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/directive.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/directive.py
deleted file mode 100644
index fdd6f0e..0000000
--- a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/directive.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2019 SiFive Inc.
-# SPDX-License-Identifier: Apache-2.0
-
-from typing import Any
-
-from cot_dt2c.pydevicetree.ast.helpers import formatLevel, wrapStrings
-
-class Directive:
-    """Represents a Devicetree directive
-
-    Directives in Devicetree source are statements of the form
-
-        /directive-name/ [option1 [option2 [...]]];
-
-    Common directive examples include:
-
-        /dts-v1/;
-        /include/ "overlay.dtsi";
-        /delete-node/ &uart0;
-        /delete-property/ status;
-
-    Their semantic meaning depends on the directive name, their location in the Devicetree,
-    and their options.
-    """
-    def __init__(self, directive: str, option: Any = None):
-        """Create a directive object"""
-        self.directive = directive
-        self.option = option
-
-    def __repr__(self) -> str:
-        return "<Directive %s>" % self.directive
-
-    def __str__(self) -> str:
-        return self.to_dts()
-
-    def to_dts(self, level: int = 0) -> str:
-        """Format the Directive in Devicetree Source format"""
-        if isinstance(self.option, list):
-            return formatLevel(level, "%s %s;\n" % (self.directive,
-                                                    wrapStrings(self.option)))
-        if isinstance(self.option, str):
-            if self.directive == "/include/":
-                return formatLevel(level, "%s \"%s\"\n" % (self.directive, self.option))
-            return formatLevel(level, "%s \"%s\";\n" % (self.directive, self.option))
-        return formatLevel(level, "%s;\n" % self.directive)
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/helpers.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/helpers.py
deleted file mode 100644
index 30c54dc..0000000
--- a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/helpers.py
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2019 SiFive Inc.
-# SPDX-License-Identifier: Apache-2.0
-
-from typing import List, Any
-
-from cot_dt2c.pydevicetree.ast.reference import Reference
-
-def formatLevel(level: int, s: str) -> str:
-    """Helper to indent a string with a number of tabs"""
-    return "\t" * level + s
-
-def wrapStrings(values: List[Any], formatHex: bool = False) -> List[Any]:
-    """Helper to wrap strings in quotes where appropriate"""
-    wrapped = []
-    for v in values:
-        if isinstance(v, Reference):
-            wrapped.append(v.to_dts())
-        elif isinstance(v, str):
-            wrapped.append("\"%s\"" % v)
-        elif isinstance(v, int):
-            if formatHex:
-                wrapped.append("0x%x" % v)
-            else:
-                wrapped.append(str(v))
-        else:
-            wrapped.append(str(v))
-    return wrapped
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/node.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/node.py
deleted file mode 100644
index d203af8..0000000
--- a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/node.py
+++ /dev/null
@@ -1,514 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2019 SiFive Inc.
-# SPDX-License-Identifier: Apache-2.0
-
-import re
-import os
-from typing import List, Union, Optional, Iterable, Callable, Any, cast, Pattern
-
-from cot_dt2c.pydevicetree.ast.helpers import formatLevel
-from cot_dt2c.pydevicetree.ast.property import Property, PropertyValues, RegArray, RangeArray
-from cot_dt2c.pydevicetree.ast.directive import Directive
-from cot_dt2c.pydevicetree.ast.reference import Label, Path, Reference, LabelReference, PathReference
-
-# Type signature for elements passed to Devicetree constructor
-ElementList = Iterable[Union['Node', Property, Directive]]
-
-# Callback type signatures for Devicetree.match() and Devicetree.chosen()
-MatchFunc = Callable[['Node'], bool]
-MatchCallback = Optional[Callable[['Node'], None]]
-ChosenCallback = Optional[Callable[[PropertyValues], None]]
-
-class Node:
-    """Represents a Devicetree Node
-
-    A Devicetree Node generally takes the form
-
-        [label:] node-name@unit-address {
-            [directives]
-            [properties]
-            [child nodes]
-        };
-
-    The structure formed by creating trees of Nodes is the bulk of any Devicetree. As the naming
-    system implies, then, each node roughly corresponds to some conceptual device, subsystem of
-    devices, bus, etc.
-
-    Devices can be referenced by label or by path, and are generally uniquely identified by a
-    collection of string identifiers assigned to the "compatible" property.
-
-    For instance, a UART device might look like
-
-        uart0: uart@10013000 {
-            compatible = "sifive,uart0";
-            reg = <0x10013000 0x1000>;
-            reg-names = "control";
-            interrupt-parent = <&plic>;
-            interrupts = <3>;
-            clocks = <&busclk>;
-            status = "okay";
-        };
-
-    This node can be identified in the following ways:
-
-        - By label: uart0
-        - By path: /path/to/uart@10013000
-        - By name: uart@10013000 (for example when referenced in a /delete-node/ directive)
-    """
-    # pylint: disable=too-many-arguments
-    def __init__(self, name: str, label: Optional[str], address: Optional[int],
-                 properties: List[Property], directives: List[Directive],
-                 children: List['Node']):
-        """Initializes a Devicetree Node
-
-        Also evaluates the /delete-node/ and /delete-property/ directives found in the node
-        and deletes the respective nodes and properties.
-        """
-        self.name = name
-        self.parent = None # type: Optional['Node']
-
-        self.label = label
-        self.address = address
-        self.properties = properties
-        self.directives = directives
-        self.children = children
-        self.ifdef = []
-
-        for d in self.directives:
-            if d.directive == "/delete-node/":
-                if isinstance(d.option, LabelReference):
-                    node = self.get_by_reference(d.option)
-                elif isinstance(d.option, str):
-                    node = self.__get_child_by_handle(d.option)
-                if node:
-                    self.remove_child(node)
-            elif d.directive == "/delete-property/":
-                # pylint: disable=cell-var-from-loop
-                properties = list(filter(lambda p: p.name == d.option, self.properties))
-                if properties:
-                    del self.properties[self.properties.index(properties[0])]
-
-    def __repr__(self) -> str:
-        if self.address:
-            return "<Node %s@%x>" % (self.name, self.address)
-        return "<Node %s>" % self.name
-
-    def __str__(self) -> str:
-        return self.to_dts()
-
-    def __eq__(self, other) -> bool:
-        return self.name == other.name and self.address == other.address
-
-    def __hash__(self):
-        return hash((self.name, self.address))
-
-    @staticmethod
-    def from_dts(source: str) -> 'Node':
-        """Create a node from Devicetree Source"""
-        # pylint: disable=import-outside-toplevel,cyclic-import
-        from pydevicetree.source import parseNode
-        return parseNode(source)
-
-    def add_child(self, node: 'Node', merge: bool = True):
-        """Add a child node and merge it into the tree"""
-        node.parent = self
-        self.children.append(node)
-        if merge:
-            self.merge_tree()
-
-    def to_dts(self, level: int = 0) -> str:
-        """Format the subtree starting at the node as Devicetree Source"""
-        out = ""
-        if isinstance(self.address, int) and self.label:
-            out += formatLevel(level,
-                               "%s: %s@%x {\n" % (self.label, self.name, self.address))
-        elif isinstance(self.address, int):
-            out += formatLevel(level, "%s@%x {\n" % (self.name, self.address))
-        elif self.label:
-            out += formatLevel(level, "%s: %s {\n" % (self.label, self.name))
-        elif self.name != "":
-            out += formatLevel(level, "%s {\n" % self.name)
-
-        for d in self.directives:
-            out += d.to_dts(level + 1)
-        for p in self.properties:
-            out += p.to_dts(level + 1)
-        for c in self.children:
-            out += c.to_dts(level + 1)
-
-        if self.name != "":
-            out += formatLevel(level, "};\n")
-
-        return out
-
-    def merge_tree(self):
-        """Recursively merge child nodes into a single tree
-
-        Parsed Devicetrees can describe the same tree multiple times, adding nodes and properties
-        each time. After parsing, this method is called to recursively merge the tree.
-        """
-        partitioned_children = []
-        for n in self.children:
-            partitioned_children.append([e for e in self.children if e == n])
-
-        new_children = []
-        for part in partitioned_children:
-            first = part[0]
-            rest = part[1:]
-            if first not in new_children:
-                for n in rest:
-                    first.merge(n)
-                new_children.append(first)
-
-        self.children = new_children
-
-        for n in self.children:
-            n.parent = self
-            n.merge_tree()
-
-    def merge(self, other: 'Node'):
-        """Merge the contents of a node into this node.
-
-        Used by Node.merge_trees()
-        """
-        if not self.label and other.label:
-            self.label = other.label
-        self.properties += other.properties
-        self.directives += other.directives
-        self.children += other.children
-        self.ifdef += other.ifdef
-
-    def get_path(self, includeAddress: bool = True) -> str:
-        """Get the path of a node (ex. /cpus/cpu@0)"""
-        if self.name == "/":
-            return ""
-        if self.parent is None:
-            return "/" + self.name
-        if isinstance(self.address, int) and includeAddress:
-            return self.parent.get_path() + "/" + self.name + "@" + ("%x" % self.address)
-        return self.parent.get_path() + "/" + self.name
-
-    def get_by_reference(self, reference: Reference) -> Optional['Node']:
-        """Get a node from the subtree by reference (ex. &label, &{/path/to/node})"""
-        if isinstance(reference, LabelReference):
-            return self.get_by_label(reference.label)
-        if isinstance(reference, PathReference):
-            return self.get_by_path(reference.path)
-
-        return None
-
-    def get_by_label(self, label: Union[Label, str]) -> Optional['Node']:
-        """Get a node from the subtree by label"""
-        matching_nodes = list(filter(lambda n: n.label == label, self.child_nodes()))
-        if len(matching_nodes) != 0:
-            return matching_nodes[0]
-        return None
-
-    def __get_child_by_handle(self, handle: str) -> Optional['Node']:
-        """Get a child node by name or name and unit address"""
-        if '@' in handle:
-            name, addr_s = handle.split('@')
-            address = int(addr_s, base=16)
-            nodes = list(filter(lambda n: n.name == name and n.address == address, self.children))
-        else:
-            name = handle
-            nodes = list(filter(lambda n: n.name == name, self.children))
-
-        if not nodes:
-            return None
-        if len(nodes) > 1:
-            raise Exception("Handle %s is ambiguous!" % handle)
-        return nodes[0]
-
-    def get_by_path(self, path: Union[Path, str]) -> Optional['Node']:
-        """Get a node in the subtree by path"""
-        matching_nodes = list(filter(lambda n: path == n.get_path(includeAddress=True), \
-                                     self.child_nodes()))
-        if len(matching_nodes) != 0:
-            return matching_nodes[0]
-
-        matching_nodes = list(filter(lambda n: path == n.get_path(includeAddress=False), \
-                                     self.child_nodes()))
-        if len(matching_nodes) != 0:
-            return matching_nodes[0]
-        return None
-
-    def filter(self, matchFunc: MatchFunc, cbFunc: MatchCallback = None) -> List['Node']:
-        """Filter all child nodes by matchFunc
-
-        If cbFunc is provided, this method will iterate over the Nodes selected by matchFunc
-        and call cbFunc on each Node
-
-        Returns a list of all matching Nodes
-        """
-        nodes = list(filter(matchFunc, self.child_nodes()))
-
-        if cbFunc is not None:
-            for n in nodes:
-                cbFunc(n)
-
-        return nodes
-
-    def match(self, compatible: Pattern, func: MatchCallback = None) -> List['Node']:
-        """Get a node from the subtree by compatible string
-
-        Accepts a regular expression to match one of the strings in the compatible property.
-        """
-        regex = re.compile(compatible)
-
-        def match_compat(node: Node) -> bool:
-            compatibles = node.get_fields("compatible")
-            if compatibles is not None:
-                return any(regex.match(c) for c in compatibles)
-            return False
-
-        return self.filter(match_compat, func)
-
-    def child_nodes(self) -> Iterable['Node']:
-        """Get an iterable over all the nodes in the subtree"""
-        for n in self.children:
-            yield n
-            for m in n.child_nodes():
-                yield m
-
-    def remove_child(self, node):
-        """Remove a child node"""
-        del self.children[self.children.index(node)]
-
-    def get_fields(self, field_name: str) -> Optional[PropertyValues]:
-        """Get all the values of a property"""
-        for p in self.properties:
-            if p.name == field_name:
-                return p.values
-        return None
-
-    def has_field(self, field_name: str) -> bool:
-        for p in self.properties:
-            if p.name == field_name:
-                return True
-        return False
-
-    def get_field(self, field_name: str) -> Any:
-        """Get the first value of a property"""
-        fields = self.get_fields(field_name)
-        if fields is not None:
-            if len(cast(PropertyValues, fields)) != 0:
-                return fields[0]
-        return None
-
-    def get_reg(self) -> Optional[RegArray]:
-        """If the node defines a `reg` property, return a RegArray for easier querying"""
-        reg = self.get_fields("reg")
-        reg_names = self.get_fields("reg-names")
-        if reg is not None:
-            if reg_names is not None:
-                return RegArray(reg.values, self.address_cells(), self.size_cells(),
-                                reg_names.values)
-            return RegArray(reg.values, self.address_cells(), self.size_cells())
-        return None
-
-    def get_ranges(self) -> Optional[RangeArray]:
-        """If the node defines a `ranges` property, return a RangeArray for easier querying"""
-        ranges = self.get_fields("ranges")
-        child_address_cells = self.get_field("#address-cells")
-        parent_address_cells = self.address_cells()
-        size_cells = self.get_field("#size-cells")
-        if ranges is not None:
-            return RangeArray(ranges.values, child_address_cells, parent_address_cells, size_cells)
-        return None
-
-    def address_cells(self):
-        """Get the number of address cells
-
-        The #address-cells property is defined by the parent of a node and describes how addresses
-        are encoded in cell arrays. If no property is defined, the default value is 2.
-        """
-        if self.parent is not None:
-            cells = self.parent.get_field("#address-cells")
-            if cells is not None:
-                return cells
-            return 2
-        return 2
-
-    def size_cells(self):
-        """Get the number of size cells
-
-        The #size-cells property is defined by the parent of a node and describes how addresses
-        are encoded in cell arrays. If no property is defined, the default value is 1.
-        """
-        if self.parent is not None:
-            cells = self.parent.get_field("#size-cells")
-            if cells is not None:
-                return cells
-            return 1
-        return 1
-
-class NodeReference(Node):
-    """A NodeReference is used to extend the definition of a previously-defined Node
-
-    NodeReferences are commonly used by Devicetree "overlays" to extend the properties of a node
-    or add child devices, such as to a bus like I2C.
-    """
-    def __init__(self, reference: Reference, properties: List[Property],
-                 directives: List[Directive], children: List[Node]):
-        """Instantiate a Node identified by reference to another node"""
-        self.reference = reference
-        Node.__init__(self, label=None, name="", address=None, properties=properties,
-                      directives=directives, children=children)
-
-    def __repr__(self) -> str:
-        return "<NodeReference %s>" % self.reference.to_dts()
-
-    def resolve_reference(self, tree: 'Devicetree') -> Node:
-        """Given the full tree, get the node being referenced"""
-        node = tree.get_by_reference(self.reference)
-        if node is None:
-            raise Exception("Node reference %s cannot be resolved" % self.reference.to_dts())
-        return cast(Node, node)
-
-    def to_dts(self, level: int = 0) -> str:
-        out = formatLevel(level, self.reference.to_dts() + " {\n")
-
-        for d in self.directives:
-            out += d.to_dts(level + 1)
-        for p in self.properties:
-            out += p.to_dts(level + 1)
-        for c in self.children:
-            out += c.to_dts(level + 1)
-
-        out += formatLevel(level, "};\n")
-
-        return out
-
-
-class Devicetree(Node):
-    """A Devicetree object describes the full Devicetree tree
-
-    This class encapsulates both the tree itself (starting at the root node /) and any Directives
-    or nodes which exist at the top level of the Devicetree Source files.
-
-    Devicetree Source files can be parsed by calling Devicetree.parseFile().
-    """
-    def __init__(self, elements: ElementList):
-        """Instantiate a Devicetree with the list of parsed elements
-
-        Resolves all reference nodes and merges the tree to combine all identical nodes.
-        """
-        properties = [] # type: List[Property]
-        directives = [] # type: List[Directive]
-        children = [] # type: List[Node]
-
-        for e in elements:
-            if isinstance(e, Node):
-                children.append(cast(Node, e))
-            elif isinstance(e, Property):
-                properties.append(cast(Property, e))
-            elif isinstance(e, Directive):
-                directives.append(cast(Directive, e))
-
-        Node.__init__(self, label=None, name="", address=None,
-                      properties=properties, directives=directives, children=children)
-
-        for node in self.children:
-            node.parent = self
-
-        reference_nodes = self.filter(lambda n: isinstance(n, NodeReference))
-        for refnode in reference_nodes:
-            refnode = cast(NodeReference, refnode)
-
-            node = refnode.resolve_reference(self)
-
-            if refnode.parent:
-                cast(Node, refnode.parent).remove_child(refnode)
-
-            node.properties += refnode.properties
-            node.directives += refnode.directives
-            node.children += refnode.children
-
-        self.merge_tree()
-
-    def __repr__(self) -> str:
-        name = self.root().get_field("compatible")
-        return "<Devicetree %s>" % name
-
-    def to_dts(self, level: int = 0) -> str:
-        """Convert the tree back to Devicetree Source"""
-        out = ""
-
-        for d in self.directives:
-            out += d.to_dts()
-        for p in self.properties:
-            out += p.to_dts()
-        for c in self.children:
-            out += c.to_dts()
-
-        return out
-
-    def get_by_path(self, path: Union[Path, str]) -> Optional[Node]:
-        """Get a node in the tree by path (ex. /cpus/cpu@0)"""
-
-        # Find and replace all aliases in the path
-        aliases = self.aliases()
-        if aliases:
-            for prop in aliases.properties:
-                if prop.name in path and len(prop.values) > 0:
-                    path = path.replace(prop.name, prop.values[0])
-
-        return self.root().get_by_path(path)
-
-    @staticmethod
-    # pylint: disable=arguments-differ
-    def from_dts(dts: str) -> 'Devicetree':
-        """Parse a string and return a Devicetree object"""
-        # pylint: disable=import-outside-toplevel,cyclic-import
-        from pydevicetree.source import parseTree
-        return parseTree(dts)
-
-    @staticmethod
-    def parseFile(filename: str, followIncludes: bool = False) -> 'Devicetree':
-        """Parse a file and return a Devicetree object"""
-        # pylint: disable=import-outside-toplevel,cyclic-import
-        from cot_dt2c.pydevicetree.source.parser import parseTree
-        with open(filename, 'r') as f:
-            contents = f.read()
-        dirname = os.path.dirname(filename)
-        if dirname != "":
-            dirname += "/"
-        return parseTree(contents, dirname, followIncludes)
-
-    @staticmethod
-    def parseStr(input: str, followIncludes: bool = False) -> 'Devicetree':
-        from cot_dt2c.pydevicetree.source.parser import parseTree
-        return parseTree(input, "", followIncludes)
-
-    def all_nodes(self) -> Iterable[Node]:
-        """Get an iterable over all nodes in the tree"""
-        return self.child_nodes()
-
-    def root(self) -> Node:
-        """Get the root node of the tree"""
-        for n in self.all_nodes():
-            if n.name == "/":
-                return n
-        raise Exception("Devicetree has no root node!")
-
-    def aliases(self) -> Optional[Node]:
-        """Get the aliases node of the tree if it exists"""
-        for n in self.all_nodes():
-            if n.name == "aliases":
-                return n
-        return None
-
-    def chosen(self, property_name: str, func: ChosenCallback = None) -> Optional[PropertyValues]:
-        """Get the values associated with one of the properties in the chosen node"""
-        def match_chosen(node: Node) -> bool:
-            return node.name == "chosen"
-
-        for n in filter(match_chosen, self.all_nodes()):
-            for p in n.properties:
-                if p.name == property_name:
-                    if func is not None:
-                        func(p.values)
-                    return p.values
-
-        return None
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/property.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/property.py
deleted file mode 100644
index d5fb687..0000000
--- a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/property.py
+++ /dev/null
@@ -1,278 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2019 SiFive Inc.
-# SPDX-License-Identifier: Apache-2.0
-
-from typing import List, Any, cast, Tuple, Optional, Iterable
-from itertools import zip_longest
-
-from cot_dt2c.pydevicetree.ast.helpers import wrapStrings, formatLevel
-
-class PropertyValues:
-    """PropertyValues is the parent class of all values which can be assigned to a Property
-
-    Child classes include
-
-        Bytestring
-        CellArray
-        StringList
-    """
-    def __init__(self, values: List[Any]):
-        """Create a PropertyValue"""
-        self.values = values
-
-    def __repr__(self) -> str:
-        return "<PropertyValues " + self.values.__repr__() + ">"
-
-    def __str__(self) -> str:
-        return self.to_dts()
-
-    def __iter__(self):
-        return iter(self.values)
-
-    def __len__(self) -> int:
-        return len(self.values)
-
-    def to_dts(self, formatHex: bool = False) -> str:
-        """Format the values in Devicetree Source format"""
-        return ", ".join(wrapStrings(self.values, formatHex))
-
-    def __getitem__(self, key) -> Any:
-        return self.values[key]
-
-    def __eq__(self, other) -> bool:
-        if isinstance(other, PropertyValues):
-            return self.values == other.values
-        return self.values == other
-
-class Bytestring(PropertyValues):
-    """A Bytestring is a sequence of bytes
-
-    In Devicetree, Bytestrings are represented as a sequence of two-digit hexadecimal integers,
-    optionally space-separated, enclosed by square brackets:
-
-        [de ad be eef]
-    """
-    def __init__(self, bytelist: List[int]):
-        """Create a Bytestring object"""
-        PropertyValues.__init__(self, cast(List[Any], bytearray(bytelist)))
-
-    def __repr__(self) -> str:
-        return "<Bytestring " + str(self.values) + ">"
-
-    def to_dts(self, formatHex: bool = False) -> str:
-        """Format the bytestring in Devicetree Source format"""
-        return "[" + " ".join("%02x" % v for v in self.values) + "]"
-
-class CellArray(PropertyValues):
-    """A CellArray is an array of integer values
-
-    CellArrays are commonly used as the value of Devicetree properties like `reg` and `interrupts`.
-    The interpretation of each element of a CellArray is device-dependent. For example, the `reg`
-    property encodes a CellArray as a list of tuples (base address, size), while the `interrupts`
-    property encodes a CellArray as simply a list of interrupt line numbers.
-    """
-    def __init__(self, cells: List[Any]):
-        """Create a CellArray object"""
-        PropertyValues.__init__(self, cells)
-
-    def __repr__(self) -> str:
-        return "<CellArray " + self.values.__repr__() + ">"
-
-    def to_dts(self, formatHex: bool = False) -> str:
-        """Format the cell array in Devicetree Source format"""
-        dtsValues = []
-        for i in self.values:
-            if not isinstance(i, OneString) and not isinstance(i, str):
-                dtsValues.append(i)
-        return "<" + " ".join(wrapStrings(dtsValues, formatHex)) + ">"
-
-class RegArray(CellArray):
-    """A RegArray is the CellArray assigned to the reg property"""
-    def __init__(self, cells: List[int],
-                 address_cells: int, size_cells: int,
-                 names: Optional[List[str]] = None):
-        """Create a RegArray from a list of ints"""
-        # pylint: disable=too-many-locals
-        CellArray.__init__(self, cells)
-        self.address_cells = address_cells
-        self.size_cells = size_cells
-
-        self.tuples = [] # type: List[Tuple[int, int, Optional[str]]]
-
-        group_size = self.address_cells + self.size_cells
-
-        if len(cells) % group_size != 0:
-            raise Exception("CellArray does not contain enough cells")
-
-        grouped_cells = [cells[i:i+group_size] for i in range(0, len(cells), group_size)]
-
-        if not names:
-            names = []
-
-        for group, name in zip_longest(grouped_cells, cast(Iterable[Any], names)):
-            address = 0
-            a_cells = list(reversed(group[:self.address_cells]))
-            for a, i in zip(a_cells, range(len(a_cells))):
-                address += (1 << (32 * i)) * a
-
-            size = 0
-            s_cells = list(reversed(group[self.address_cells:]))
-            for s, i in zip(s_cells, range(len(s_cells))):
-                size += (1 << (32 * i)) * s
-
-            self.tuples.append(cast(Tuple[int, int, Optional[str]], tuple([address, size, name])))
-
-    def get_by_name(self, name: str) -> Optional[Tuple[int, int]]:
-        """Returns the (address, size) tuple with a given name"""
-        for t in self.tuples:
-            if t[2] == name:
-                return cast(Tuple[int, int], tuple(t[:2]))
-        return None
-
-    def __repr__(self) -> str:
-        return "<RegArray " + self.values.__repr__() + ">"
-
-    def __iter__(self) -> Iterable[Tuple[int, int]]:
-        return cast(Iterable[Tuple[int, int]], map(lambda t: tuple(t[:2]), self.tuples))
-
-    def __len__(self) -> int:
-        return len(self.tuples)
-
-    def __getitem__(self, key) -> Optional[Tuple[int, int]]:
-        return list(self.__iter__())[key]
-
-class RangeArray(CellArray):
-    """A RangeArray is the CellArray assigned to the range property"""
-    def __init__(self, cells: List[int], child_address_cells: int,
-                 parent_address_cells: int, size_cells: int):
-        """Create a RangeArray from a list of ints"""
-        # pylint: disable=too-many-locals
-        CellArray.__init__(self, cells)
-        self.child_address_cells = child_address_cells
-        self.parent_address_cells = parent_address_cells
-        self.size_cells = size_cells
-
-        self.tuples = [] # type: List[Tuple[int, int, int]]
-
-        group_size = self.child_address_cells + self.parent_address_cells + self.size_cells
-
-        if len(cells) % group_size != 0:
-            raise Exception("CellArray does not contain enough cells")
-
-        grouped_cells = [cells[i:i+group_size] for i in range(0, len(cells), group_size)]
-
-        def sum_cells(cells: List[int]):
-            value = 0
-            for cell, index in zip(list(reversed(cells)), range(len(cells))):
-                value += (1 << (32 * index)) * cell
-            return value
-
-        for group in grouped_cells:
-            child_address = sum_cells(group[:self.child_address_cells])
-            parent_address = sum_cells(group[self.child_address_cells: \
-                                             self.child_address_cells + self.parent_address_cells])
-            size = sum_cells(group[self.child_address_cells + self.parent_address_cells:])
-
-            self.tuples.append(cast(Tuple[int, int, int],
-                                    tuple([child_address, parent_address, size])))
-
-    def __repr__(self) -> str:
-        return "<RangeArray " + self.values.__repr__() + ">"
-
-    def __iter__(self):
-        return iter(self.tuples)
-
-    def __len__(self) -> int:
-        return len(self.tuples)
-
-    def __getitem__(self, key) -> Any:
-        return self.tuples[key]
-
-class StringList(PropertyValues):
-    """A StringList is a list of null-terminated strings
-
-    The most common use of a StringList in Devicetree is to describe the `compatible` property.
-    """
-    def __init__(self, strings: List[str]):
-        """Create a StringList object"""
-        PropertyValues.__init__(self, strings)
-
-    def __repr__(self) -> str:
-        return "<StringList " + self.values.__repr__() + ">"
-
-    def to_dts(self, formatHex: bool = False) -> str:
-        """Format the list of strings in Devicetree Source format"""
-        return ", ".join(wrapStrings(self.values))
-
-class OneString(PropertyValues):
-    def __init__(self, string: str):
-        PropertyValues.__init__(self, string)
-
-    def __repr__(self) -> str:
-        return self.values.__repr__()
-
-    def to_dts(self, formatHex: bool = False) -> str:
-        return super().to_dts(formatHex)
-
-class Property:
-    """A Property is a key-value pair for a Devicetree Node
-
-    Properties are used to describe Nodes in the tree. There are many common properties, like
-
-        - compatible
-        - reg
-        - reg-names
-        - ranges
-        - interrupt-controller
-        - interrupts
-        - interrupt-parent
-        - clocks
-        - status
-
-    Which might commonly describe many or all nodes in a tree, and there are device, vendor,
-    operating system, runtime-specific properties.
-
-    Properties can possess no value, conveing meaning solely by their presence:
-
-        interrupt-controller;
-
-    Properties can also possess values such as an array of cells, a list of strings, etc.
-
-        reg = <0x10013000 0x1000>;
-        compatible = "sifive,rocket0", "riscv";
-
-    And properties can posses arbitrarily complex values, such as the following from the
-    Devicetree specification:
-
-        example = <0xf00f0000 19>, "a strange property format";
-    """
-    def __init__(self, name: str, values: PropertyValues):
-        """Create a Property object"""
-        self.name = name
-        self.values = values
-
-    def __repr__(self) -> str:
-        return "<Property %s>" % self.name
-
-    def __str__(self) -> str:
-        return self.to_dts()
-
-    @staticmethod
-    def from_dts(dts: str) -> 'Property':
-        """Parse a file and return a Devicetree object"""
-        # pylint: disable=import-outside-toplevel,cyclic-import
-        from pydevicetree.source import parseProperty
-        return parseProperty(dts)
-
-    def to_dts(self, level: int = 0) -> str:
-        """Format the Property assignment in Devicetree Source format"""
-        if self.name in ["reg", "ranges"]:
-            value = self.values.to_dts(formatHex=True)
-        else:
-            value = self.values.to_dts(formatHex=False)
-
-        if value != "":
-            return formatLevel(level, "%s = %s;\n" % (self.name, value))
-        if self.name == "ifdef":
-            return ""
-        return formatLevel(level, "%s;\n" % self.name)
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/reference.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/reference.py
deleted file mode 100644
index 54b2d28..0000000
--- a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/reference.py
+++ /dev/null
@@ -1,111 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2019 SiFive Inc.
-# SPDX-License-Identifier: Apache-2.0
-
-from typing import Union, Iterator
-
-class Label:
-    """A Label is a unique identifier for a Node
-
-    For example, the following node has the label "uart0":
-
-        uart0: uart@10013000 {
-            ...
-        };
-    """
-    def __init__(self, name: str):
-        """Create a Label"""
-        self.name = name
-
-    def __repr__(self) -> str:
-        return "<Label " + self.name + ">"
-
-    def __eq__(self, other: object) -> bool:
-        if isinstance(other, Label):
-            return self.name == other.name
-        if isinstance(other, str):
-            return self.name == other
-        return False
-
-    def to_dts(self) -> str:
-        """Format the label in Devicetree Source format"""
-        return self.name + ":"
-
-class Path:
-    """A Path uniquely identifies a Node by its parents and (optionally) unit address"""
-    def __init__(self, path: str):
-        """Create a path out of a string"""
-        self.path = path
-
-    def to_dts(self) -> str:
-        """Format the Path in Devicetree Source format"""
-        return self.path
-
-    def __repr__(self) -> str:
-        return "<Path " + self.to_dts() + ">"
-
-    def __eq__(self, other: object) -> bool:
-        if isinstance(other, Path):
-            return self.to_dts() == other.to_dts()
-        if isinstance(other, str):
-            return self.to_dts() == other
-        return False
-
-    def __iter__(self) -> Iterator[str]:
-        return iter(self.path.split("/"))
-
-    def replace(self, old: str, new: str) -> 'Path':
-        """Replace any elements of the path which match 'old' with a new element 'new'"""
-        return Path(self.path.replace(old, new))
-
-class Reference:
-    """A Reference is a Devicetree construct which points to a Node in the tree
-
-    The following are types of references:
-
-        - A reference to a label:
-
-            &my-label;
-
-        - A reference to a node by path:
-
-            &{/path/to/node@deadbeef}
-
-    This is the parent class for both types of references, LabelReference and PathReference
-    """
-    # pylint: disable=no-self-use
-    def to_dts(self, formatHex: bool = False) -> str:
-        """Format the Reference in Devicetree Source format"""
-        return ""
-
-class LabelReference(Reference):
-    """A LabelReference is a reference to a Node by label"""
-    def __init__(self, label: Union[Label, str]):
-        """Create a LabelReference from a Label or string"""
-        if isinstance(label, Label):
-            self.label = label
-        elif isinstance(label, str):
-            self.label = Label(label)
-
-    def __repr__(self) -> str:
-        return "<LabelReference " + self.to_dts() + ">"
-
-    def to_dts(self, formatHex: bool = False) -> str:
-        """Format the LabelReference in Devicetree Source format"""
-        return "&" + self.label.name
-
-class PathReference(Reference):
-    """A PathReference is a reference to a Node by path"""
-    def __init__(self, path: Union[Path, str]):
-        """Create a PathReference from a Path or string"""
-        if isinstance(path, Path):
-            self.path = path
-        elif isinstance(path, str):
-            self.path = Path(path)
-
-    def __repr__(self) -> str:
-        return "<PathReference " + self.to_dts() + ">"
-
-    def to_dts(self, formatHex: bool = False) -> str:
-        """Format the PathReference in Devicetree Source format"""
-        return "&{" + self.path.to_dts() + "}"
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/source/__init__.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/__init__.py
deleted file mode 100644
index 96768b3..0000000
--- a/tools/cot_dt2c/cot_dt2c/pydevicetree/source/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2019 SiFive Inc.
-# SPDX-License-Identifier: Apache-2.0
-
-from cot_dt2c.pydevicetree.source.parser import parseTree, parseNode, parseProperty
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/source/grammar.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/grammar.py
deleted file mode 100644
index fb165e1..0000000
--- a/tools/cot_dt2c/cot_dt2c/pydevicetree/source/grammar.py
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2019 SiFive Inc.
-# SPDX-License-Identifier: Apache-2.0
-
-import os
-import sys
-
-import pyparsing as p # type: ignore
-
-ENV_CACHE_OPTION = "PYDEVICETREE_CACHE_SIZE_BOUND"
-
-cache_bound = None
-if ENV_CACHE_OPTION in os.environ:
-    option = os.environ[ENV_CACHE_OPTION]
-    if option != "None":
-        try:
-            cache_bound = int(option)
-        except ValueError:
-            print("%s requires a valid integer" % ENV_CACHE_OPTION, file=sys.stderr)
-p.ParserElement.enablePackrat(cache_bound)
-
-node_name = p.Word(p.alphanums + ",.-+_") ^ p.Literal("/")
-integer = p.pyparsing_common.integer ^ (p.Literal("0x").suppress() + p.pyparsing_common.hex_integer)
-unit_address = p.pyparsing_common.hex_integer
-unit_addresses = p.delimitedList(unit_address("address"), delim=",")
-node_handle = node_name("node_name") + p.Optional(p.Literal("@") + unit_addresses)
-property_name = p.Word(p.alphanums + ",.-_+?#")
-label = p.Word(p.alphanums + "_").setResultsName("label")
-label_creation = p.Combine(label + p.Literal(":"))
-string = p.QuotedString(quoteChar='"')
-stringlist = p.delimitedList(string)
-node_path = p.Combine(p.Literal("/") + \
-        p.delimitedList(node_handle, delim="/", combine=True)).setResultsName("path")
-path_reference = p.Literal("&{").suppress() + node_path + p.Literal("}").suppress()
-label_reference = p.Literal("&").suppress() + label
-label_raw = p.Word(p.alphanums + "_")
-reference = path_reference ^ label_reference ^ label_raw
-include_directive = p.Literal("/include/") + p.QuotedString(quoteChar='"')
-generic_directive = p.QuotedString(quoteChar="/", unquoteResults=False) + \
-        p.Optional(string ^ property_name ^ node_name ^ reference ^ (integer * 2)) + \
-        p.Literal(";").suppress()
-directive = include_directive ^ generic_directive
-
-operator = p.oneOf("~ ! * / + - << >> < <= > >= == != & ^ | && ||")
-arith_expr = p.Forward()
-ternary_element = arith_expr ^ integer
-ternary_expr = ternary_element + p.Literal("?") + ternary_element + p.Literal(":") + ternary_element
-arith_expr = p.nestedExpr(content=(p.OneOrMore(operator ^ integer) ^ ternary_expr))
-arth_str = p.Forward()
-arith_str_expr = p.nestedExpr(content=(p.OneOrMore(operator ^ integer ^ label_raw ^ p.Literal(",")) ^ ternary_expr))
-
-label_list = p.OneOrMore(p.Combine(label + p.Literal("\n")))
-
-cell_array = p.Literal("<").suppress() + \
-        p.ZeroOrMore(integer ^ arith_expr ^ arith_str_expr ^ label_list ^ string ^ reference ^ label_creation.suppress()) + \
-        p.Literal(">").suppress()
-bytestring = p.Literal("[").suppress() + \
-        (p.OneOrMore(p.Word(p.hexnums, exact=2) ^ label_creation.suppress())) + \
-        p.Literal("]").suppress()
-property_values = p.Forward()
-property_values = p.delimitedList(property_values ^ cell_array ^ bytestring ^ stringlist ^ \
-                                  reference ^ label_raw)
-property_assignment = property_name("property_name") + p.Optional(p.Literal("=").suppress() + \
-        (property_values)).setResultsName("value") + p.Optional(p.Literal(";").suppress())
-
-ifdef_label = p.ZeroOrMore(p.Word(p.alphanums + " _|//*=/(/)"))
-ifdef_define = p.Combine(p.Keyword("#if") + ifdef_label)
-ifdef_end = p.Combine(p.Keyword("#endif") + ifdef_label)
-ifdef_define_values = p.Forward()
-ifdef_define_values = p.ZeroOrMore(ifdef_define)
-ifdef_end_values = p.Forward()
-ifdef_end_values = p.ZeroOrMore(ifdef_end)
-
-node_opener = ifdef_define_values + p.Optional(label_creation) + node_handle + p.Literal("{").suppress()
-node_reference_opener = reference + p.Literal("{").suppress()
-node_closer = p.Literal("}").suppress() + p.Literal(";").suppress() + ifdef_end_values
-node_definition = p.Forward()
-# pylint: disable=expression-not-assigned
-node_definition << (node_opener ^ node_reference_opener) + \
-        p.ZeroOrMore(property_assignment ^ directive ^ node_definition ^ ifdef_define ^ ifdef_end) + \
-        node_closer
-
-devicetree = p.ZeroOrMore(directive ^ node_definition)
-
-devicetree.ignore(p.cStyleComment)
-devicetree.ignore("//" + p.SkipTo(p.lineEnd))
-devicetree.ignore("#include" + p.SkipTo(p.lineEnd))
-devicetree.ignore("#define" + p.SkipTo(p.lineEnd))
-devicetree.ignore("#else" + p.SkipTo(p.lineEnd))
-devicetree.ignore("#error" + p.SkipTo(p.lineEnd))
-devicetree.ignore("#ifndef" + p.SkipTo(p.lineEnd))
-
-if __name__ == "__main__":
-    if len(sys.argv) > 1:
-        devicetree.parseFile(sys.argv[1]).pprint()
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/source/parser.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/parser.py
deleted file mode 100644
index 0692482..0000000
--- a/tools/cot_dt2c/cot_dt2c/pydevicetree/source/parser.py
+++ /dev/null
@@ -1,238 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2019 SiFive Inc.
-# SPDX-License-Identifier: Apache-2.0
-
-from itertools import chain
-
-from cot_dt2c.pydevicetree.source import grammar
-from cot_dt2c.pydevicetree.ast import *
-
-ifdef_stack = []
-
-def transformNode(string, location, tokens):
-    """Transforms a ParseResult into a Node"""
-    properties = [e for e in tokens.asList() if isinstance(e, Property)]
-    directives = [e for e in tokens.asList() if isinstance(e, Directive)]
-    children = [e for e in tokens.asList() if isinstance(e, Node)]
-
-    if isinstance(tokens[0], Reference):
-        return NodeReference(tokens[0], properties=properties,
-                             directives=directives, children=children)
-    return Node(tokens.node_name, tokens.label, tokens.address, properties=properties,
-                directives=directives, children=children)
-
-def transformPropertyAssignment(string, location, tokens):
-    """Transforms a ParseResult into a Property"""
-    for v in tokens.value:
-        if isinstance(v, PropertyValues):
-            return Property(tokens.property_name, v)
-        if isinstance(v, CellArray):
-            return Property(tokens.property_name, v)
-        if isinstance(v, StringList):
-            return Property(tokens.property_name, v)
-        if isinstance(v, Reference):
-            return Property(tokens.property_name, v)
-
-    return Property(tokens.property_name, PropertyValues([]))
-
-def transformDirective(string, location, tokens):
-    """Transforms a ParseResult into a Directive"""
-    if len(tokens.asList()) > 1:
-        return Directive(tokens[0], tokens[1])
-    return Directive(tokens[0])
-
-def evaluateArithExpr(string, location, tokens):
-    """Evaluates a ParseResult as a python expression"""
-    flat_tokens = list(chain.from_iterable(tokens.asList()))
-    expr = " ".join(str(t) for t in flat_tokens)
-    # pylint: disable=eval-used
-    return eval(expr)
-
-def transformTernary(string, location, tokens):
-    """Evaluates a ParseResult as a ternary expression"""
-    # pylint: disable=eval-used
-    return eval(str(tokens[2]) +" if " + str(tokens[0]) + " else " + str(tokens[4]))
-
-def transformPropertyValues(string, location, tokens):
-    """Transforms a ParseResult into a PropertyValues"""
-    if len(tokens.asList()) == 1:
-        return tokens.asList()[0]
-    return PropertyValues(tokens.asList())
-
-def transformStringList(string, location, tokens):
-    """Transforms a ParseResult into a StringList"""
-    return StringList(tokens.asList())
-
-def transformString(string, location, token):
-    return OneString(token)
-
-def transformIfdefMacro(string, location, tokens):
-    tokenlist = tokens.asList()
-    for t in tokenlist:
-        ifdef_stack.append(t)
-    return Property("ifdef", PropertyValues(ifdef_stack.copy()))
-
-def transformIfdefEnd(string, location, tokens):
-    tokenlist = tokens.asList()
-    for t in tokenlist:
-        ifdef_stack.pop()
-
-def transformIfdef(string, location, tokens):
-    return Property("ifdef", PropertyValues(tokens))
-
-def evaluateStrArithExpr(string, location, tokens):
-    """Evaluates a ParseResult as a python expression"""
-    flat_tokens = list(chain.from_iterable(tokens.asList()))
-    for i, t in enumerate(flat_tokens):
-        if isinstance(t, int):
-            flat_tokens[i] = "(" + str(t) + ")"
-    expr = " ".join(str(t) for t in flat_tokens)
-    # pylint: disable=eval-used
-    return expr
-
-def transformBytestring(string, location, tokens):
-    """Transforms a ParseResult into a Bytestring"""
-    inttokens = []
-    for t in tokens.asList():
-        if all(c in "0123456789abcdefABCDEF" for c in t):
-            inttokens.append(int(t, base=16))
-    return Bytestring(inttokens)
-
-def transformCellArray(string, location, tokens):
-    """Transforms a ParseResult into a CellArray"""
-    return CellArray(tokens.asList())
-
-def transformLabel(string, location, tokens):
-    """Transforms a ParseResult into a Label"""
-    return Label(tokens.label)
-
-def transformPath(string, location, tokens):
-    """Transforms a ParseResult into a Path"""
-    path = ""
-    for handle in tokens.path[0].split("/"):
-        if "@" in handle:
-            node, address = handle.split("@")
-            path += "/%s@%x" % (node, int(address))
-        elif handle != "":
-            path += "/" + handle
-    return Path(path)
-
-def transformPathReference(string, location, tokens):
-    """Transforms a ParseResult into a PathReference"""
-    return PathReference(tokens[0])
-
-def transformLabelReference(string, location, tokens):
-    """Transforms a ParseResult into a LabelReference"""
-    return LabelReference(tokens[0])
-
-def transformReference(string, location, tokens):
-    """Transforms a ParseResult into a Reference"""
-    if isinstance(tokens[0], Reference):
-        return tokens[0]
-    return None
-
-grammar.label.setParseAction(transformLabel)
-grammar.node_path.setParseAction(transformPath)
-grammar.path_reference.setParseAction(transformPathReference)
-grammar.label_reference.setParseAction(transformLabelReference)
-grammar.reference.setParseAction(transformReference)
-grammar.node_definition.setParseAction(transformNode)
-grammar.property_assignment.setParseAction(transformPropertyAssignment)
-grammar.directive.setParseAction(transformDirective)
-grammar.arith_expr.setParseAction(evaluateArithExpr)
-grammar.ternary_expr.setParseAction(transformTernary)
-grammar.stringlist.setParseAction(transformStringList)
-grammar.bytestring.setParseAction(transformBytestring)
-grammar.cell_array.setParseAction(transformCellArray)
-grammar.property_values.setParseAction(transformPropertyValues)
-grammar.label_raw.setParseAction(transformString)
-grammar.ifdef_define_values.setParseAction(transformIfdefMacro)
-grammar.ifdef_end_values.setParseAction(transformIfdefEnd)
-grammar.arith_str_expr.setParseAction(transformPropertyValues)
-
-def printTree(tree, level=0):
-    """Helper function to print a bunch of elements as a tree"""
-    def printlevel(level, s):
-        print(" " * level + s)
-
-    for item in tree:
-        if isinstance(item, Node):
-            if item.address:
-                printlevel(level, "Node %s@%x" % (item.name, item.address))
-            else:
-                printlevel(level, "Node %s" % item.name)
-
-            if item.label:
-                printlevel(level, " Label: %s" % item.label)
-
-            if item.parent:
-                printlevel(level, " Parent: %s" % item.parent)
-
-            printTree(item.properties, level=(level + 1))
-
-            printTree(item.children, level=(level + 1))
-        elif isinstance(item, Property):
-            if item.values:
-                printlevel(level, "Property %s: %s" % (item.name, item.values))
-            else:
-                printlevel(level, "Property %s" % item.name)
-        elif isinstance(item, Directive):
-            if item.options:
-                printlevel(level, "Directive %s: %s" % (item.directive, item.options))
-            else:
-                printlevel(level, "Directive %s" % item.directive)
-
-def parentNodes(tree, parent=None):
-    """Walks a tree and sets Nodes' parent field to point at their parent"""
-    for item in tree:
-        if isinstance(item, Node):
-            item.parent = parent
-            parentNodes(item.children, item)
-
-def recurseIncludeFiles(elements, pwd):
-    """Recursively follows and parses /include/ directives an a tree"""
-    for e in elements:
-        if isinstance(e, Directive):
-            if e.directive == "/include/":
-                # Prefix with current directory if path is not absolute
-                if e.option[0] != '/':
-                    e.option = pwd + e.option
-
-                with open(e.option, 'r') as f:
-                    contents = f.read()
-
-                elements += parseElements(contents)
-
-                del elements[elements.asList().index(e)]
-
-def parseElements(dts, pwd="", followIncludes=False):
-    """Parses a string into a list of elements"""
-    elements = grammar.devicetree.parseString(dts, parseAll=True)
-    parentNodes(elements)
-    if followIncludes:
-        recurseIncludeFiles(elements, pwd)
-    return elements
-
-def parseTree(dts, pwd="", followIncludes=False):
-    """Parses a string into a full Devicetree"""
-    return Devicetree(parseElements(dts, pwd, followIncludes))
-
-def parseNode(dts):
-    """Parses a string into a Devictreee Node"""
-    return grammar.node_definition.parseString(dts, parseAll=True)[0]
-
-def parseProperty(dts):
-    """Parses a string into a Devicetree Property"""
-    return grammar.property_assignment.parseString(dts, parseAll=True)[0]
-
-if __name__ == "__main__":
-    import sys
-    if len(sys.argv) > 1:
-        with open(sys.argv[1], 'r') as f:
-            dts = f.read()
-        tree = parseTree(dts)
-        printTree(tree)
-        print(tree)
-    else:
-        print("Please pass the devicetree source file as an argument")
-        sys.exit(1)
diff --git a/tools/cot_dt2c/poetry.lock b/tools/cot_dt2c/poetry.lock
new file mode 100644
index 0000000..df58d54
--- /dev/null
+++ b/tools/cot_dt2c/poetry.lock
@@ -0,0 +1,334 @@
+# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
+
+[[package]]
+name = "atomicwrites"
+version = "1.4.1"
+description = "Atomic file writes."
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+    {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"},
+]
+
+[[package]]
+name = "attrs"
+version = "24.2.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"},
+    {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"},
+]
+
+[package.extras]
+benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
+tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
+
+[[package]]
+name = "click"
+version = "8.1.7"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+    {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+    {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+    {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "igraph"
+version = "0.11.6"
+description = "High performance graph data structures and algorithms"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "igraph-0.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3f8b837181e8e87676be3873ce87cc92cc234efd58a2da2f6b4e050db150fcf4"},
+    {file = "igraph-0.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:245c4b7d7657849eff80416f5df4525c8fc44c74a981ee4d44f0ef2612c3bada"},
+    {file = "igraph-0.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdb7be3d165073c0136295c0808e9edc57ba096cdb26e94086abb04561f7a292"},
+    {file = "igraph-0.11.6-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58974e20df2986a1ae52a16e51ecb387cc0cbeb41c5c0ddff4d373a1bbf1d9c5"},
+    {file = "igraph-0.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bef14de5e8ab70724a43808b1ed14aaa6fe1002f87e592289027a3827a8f44a"},
+    {file = "igraph-0.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:86c1e98de2e32d074df8510bf18abfa1f4c5fda4cb28a009985a5d746b0c0125"},
+    {file = "igraph-0.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ebc5b3d702158abeb2e4d2414374586a2b932e1a07e48352b470600e1733d528"},
+    {file = "igraph-0.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0efe6d0fb22d3987a800eb3857ed04df9eb4c5dddd0998be05232cb646f1c337"},
+    {file = "igraph-0.11.6-cp38-cp38-win32.whl", hash = "sha256:f4e68b27497b1c8ada2fb2bc35ef3fa7b0d72e84306b3d648d3de240fc618c32"},
+    {file = "igraph-0.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:5665b33dfbfca5f54ce9b4fea6b97903bd0e99fb1b02acf5e57e600bdfa5a355"},
+    {file = "igraph-0.11.6-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8aabef03d787b519d1075dfc0da4a1109fb113b941334883e3e7947ac30a459e"},
+    {file = "igraph-0.11.6-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1f2cc4a518d99cdf6cae514f85e93e56852bc8c325b3abb96037d1d690b5975f"},
+    {file = "igraph-0.11.6-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e859238be52ab8ccc614d18f9362942bc88ce543afc12548f81ae99b10801d"},
+    {file = "igraph-0.11.6-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d61fbe5e85eb4ae9efe08c461f9bdeedb02a2b5739fbc223d324a71f40a28be2"},
+    {file = "igraph-0.11.6-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6620ba39df29fd42151becf82309b54e57148233c9c3ef890eed62e25eed8a5"},
+    {file = "igraph-0.11.6-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59666589bb3d07f310cda2c5106a8adeeb77c2ef27fecf1c6438b6091f4ca69d"},
+    {file = "igraph-0.11.6-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:8750b6d6caebf199cf7dc41c931f58e330153779707391e30f0a29f02666fb6e"},
+    {file = "igraph-0.11.6-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:967d6f2c30fe94317da15e459374d0fb8ca3e56020412f201ecd07dd5b5352f2"},
+    {file = "igraph-0.11.6-cp39-abi3-win32.whl", hash = "sha256:9744f95a67319eb6cb487ceabf30f5d7940de34bada51f0ba63adbd23e0f94ad"},
+    {file = "igraph-0.11.6-cp39-abi3-win_amd64.whl", hash = "sha256:b80e69eb11faa9c57330a9ffebdde5808966efe1c1f638d4d4827ea04df7aca8"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0329c16092e2ea7930d5f8368666ce7cb704900cc0ea04e4afe9ea1dd46e44af"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21752313f449bd8688e5688e95ea7231cea5e9199c7162535029be0d9af848ac"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea25e136c6c4161f53ff58868b23ff6c845193050ab0e502236d68e5d4174e32"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac84433a03aef15e4b810010b08882b09854a3669450ccf31e392dbe295d2a66"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac697a44e3573169fa2b28c9c37dcf9cf01e0f558b845dd7123860d4c7c8fb89"},
+    {file = "igraph-0.11.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bdeae8bf35316eb1fb27bf667dcf5ecf5fcfb0b8f51831bc1b00c39c09c2d73b"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ad7e4aa442935de72554b96733bf6d7f09eac5cee97988a2562bdd3ca173cfa3"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:8d2818780358a686178866d01568b9df1f29678581734ad7a78882bab54df004"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2352276a20d979f1dea360af4202bb9f0c9a7d2c77f51815c0e625165e82013d"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:687fdab543b507d622fa3043f4227e5b26dc61dcf8ff8c0919fccddcc655f8b8"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57f7f8214cd48c9a4d97f7346a4152ba2d4ac95fb5ee0df4ecf224fce4ba3d14"},
+    {file = "igraph-0.11.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2b9cc69ede53f76ffae03b066609aa90184dd68ef15da8c104a97cebb9210838"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:591e1e447c3f0092daf7613a3eaedab83f9a0b0adbaf7702724c5117ded038a5"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ca558eb331bc687bc33e5cd23717e22676e9412f8cda3a31d30c996a0487610d"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf43c30e08debb087c9e3da69aa5cf1b6732968da34d55a614e3421b9a452146"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d38e8d7db72b187d9d2211d0d06b3271fa9f32b04d49d789e2859b5480db0d0"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a318b059051ff78144a1c3cb880f4d933c812bcdb3d833a49cd7168d0427672"},
+    {file = "igraph-0.11.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c54027add809b3c5b6685b8deca4ea4763fd000b9ea45c7ee46b7c9d61ff15e"},
+    {file = "igraph-0.11.6.tar.gz", hash = "sha256:837f233256c3319f2a35a6a80d94eafe47b43791ef4c6f9e9871061341ac8e28"},
+]
+
+[package.dependencies]
+texttable = ">=1.6.2"
+
+[package.extras]
+cairo = ["cairocffi (>=1.2.0)"]
+doc = ["Sphinx (>=7.0.0)", "pydoctor (>=23.4.0)", "sphinx-gallery (>=0.14.0)", "sphinx-rtd-theme (>=1.3.0)"]
+matplotlib = ["matplotlib (>=3.6.0)"]
+plotly = ["plotly (>=5.3.0)"]
+plotting = ["cairocffi (>=1.2.0)"]
+test = ["Pillow (>=9)", "cairocffi (>=1.2.0)", "matplotlib (>=3.6.0)", "networkx (>=2.5)", "numpy (>=1.19.0)", "pandas (>=1.1.0)", "plotly (>=5.3.0)", "pytest (>=7.0.1)", "pytest-timeout (>=2.1.0)", "scipy (>=1.5.0)"]
+test-musl = ["cairocffi (>=1.2.0)", "networkx (>=2.5)", "pytest (>=7.0.1)", "pytest-timeout (>=2.1.0)"]
+
+[[package]]
+name = "iniconfig"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+    {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+]
+
+[[package]]
+name = "mypy"
+version = "0.910"
+description = "Optional static typing for Python"
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
+    {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
+    {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"},
+    {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"},
+    {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"},
+    {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"},
+    {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"},
+    {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"},
+    {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"},
+    {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"},
+    {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"},
+    {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"},
+    {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"},
+    {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"},
+    {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"},
+    {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"},
+    {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"},
+    {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"},
+    {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"},
+    {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"},
+    {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"},
+    {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"},
+    {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"},
+]
+
+[package.dependencies]
+mypy-extensions = ">=0.4.3,<0.5.0"
+toml = "*"
+typing-extensions = ">=3.7.4"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+python2 = ["typed-ast (>=1.4.0,<1.5.0)"]
+
+[[package]]
+name = "mypy-extensions"
+version = "0.4.4"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+optional = false
+python-versions = ">=2.7"
+files = [
+    {file = "mypy_extensions-0.4.4.tar.gz", hash = "sha256:c8b707883a96efe9b4bb3aaf0dcc07e7e217d7d8368eec4db4049ee9e142f4fd"},
+]
+
+[[package]]
+name = "packaging"
+version = "24.1"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
+    {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
+]
+
+[[package]]
+name = "plotly"
+version = "5.23.0"
+description = "An open-source, interactive data visualization library for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "plotly-5.23.0-py3-none-any.whl", hash = "sha256:76cbe78f75eddc10c56f5a4ee3e7ccaade7c0a57465546f02098c0caed6c2d1a"},
+    {file = "plotly-5.23.0.tar.gz", hash = "sha256:89e57d003a116303a34de6700862391367dd564222ab71f8531df70279fc0193"},
+]
+
+[package.dependencies]
+packaging = "*"
+tenacity = ">=6.2.0"
+
+[[package]]
+name = "pluggy"
+version = "1.5.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
+    {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "py"
+version = "1.11.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+files = [
+    {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
+    {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
+]
+
+[[package]]
+name = "pydevicetree"
+version = "0.0.13"
+description = "A library for parsing Devicetree Source v1"
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "pydevicetree-0.0.13-py3-none-any.whl", hash = "sha256:d61c695cec925b90a8b5740053f4b604e51154a9b36e62a2f12ed9ceaf2f8c38"},
+    {file = "pydevicetree-0.0.13.tar.gz", hash = "sha256:5700c05df89bad8fd729c11aa6f764a3323bcb3796f13b32481ae34445cfc1b7"},
+]
+
+[package.dependencies]
+pyparsing = "*"
+
+[[package]]
+name = "pyparsing"
+version = "3.1.2"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+optional = false
+python-versions = ">=3.6.8"
+files = [
+    {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"},
+    {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"},
+]
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
+name = "pytest"
+version = "6.2.5"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
+    {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
+]
+
+[package.dependencies]
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+attrs = ">=19.2.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+py = ">=1.8.2"
+toml = "*"
+
+[package.extras]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+
+[[package]]
+name = "tenacity"
+version = "9.0.0"
+description = "Retry code until it succeeds"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"},
+    {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"},
+]
+
+[package.extras]
+doc = ["reno", "sphinx"]
+test = ["pytest", "tornado (>=4.5)", "typeguard"]
+
+[[package]]
+name = "texttable"
+version = "1.7.0"
+description = "module to create simple ASCII tables"
+optional = false
+python-versions = "*"
+files = [
+    {file = "texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917"},
+    {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"},
+]
+
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+    {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+    {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.12.2"
+description = "Backported and Experimental Type Hints for Python 3.8+"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
+    {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
+]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.8"
+content-hash = "afa5cb49be96467a848bab753a630c6f5ec42d6750d67d29920c3e3971774e36"
diff --git a/tools/cot_dt2c/pyproject.toml b/tools/cot_dt2c/pyproject.toml
index d383924..73251d7 100644
--- a/tools/cot_dt2c/pyproject.toml
+++ b/tools/cot_dt2c/pyproject.toml
@@ -28,30 +28,33 @@
 [tool.poetry.dependencies]
 python = "^3.8"
 click = "^8.1.7"
-pyparsing = "^2.4.7"
 plotly = "^5.23.0"
-pandas = "^2.2.2"
+pydevicetree = "0.0.13"
 igraph = "^0.11.6"
+pyparsing = "^3.1.2"
 
-[tool.poetry.dev-dependencies]
+[tool.poetry.group.dev]
+optional = true
+
+[tool.poetry.group.dev.dependencies]
 mypy = "^0.910"
 pytest = "^6.2.5"
-pyparsing = "^2.4.7"
-plotly = "^5.23.0"
-pandas = "^2.2.2"
-igraph = "^0.11.6"
 
 [tool.mypy]
 # https://mypy.readthedocs.io/en/latest/config_file.html#using-a-pyproject-toml-file
-python_version = 3.8
+python_version = "3.8"
 pretty = true
 show_traceback = true
 color_output = true
 
+[[tool.mypy.overrides]]
+module = ["igraph", "pydevicetree", "pydevicetree.ast", "plotly", "plotly.graph_objects"]
+ignore_missing_imports = true
+
 [tool.coverage.run]
 source = ["tests"]
 
-[coverage.paths]
+[tool.coverage.paths]
 source = "cot_dt2c"
 
 [tool.poetry.scripts]
diff --git a/tools/cot_dt2c/requirements.txt b/tools/cot_dt2c/requirements.txt
deleted file mode 100644
index 246b81d..0000000
--- a/tools/cot_dt2c/requirements.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-mypy
-pylint
-pyparsing
-igraph
-pandas
-plotly