feat(auth): standalone CoT dt2c tool

Add the standalone CoT dt2c tool for CoT DTB conversion to
c file

Change-Id: If28e580a4c2825f5dc9008e93cd2aae3fc173e73
Signed-off-by: Xialin Liu <Xialin.Liu@ARM.com>
diff --git a/tools/cot_dt2c/Makefile b/tools/cot_dt2c/Makefile
new file mode 100644
index 0000000..ad8d9f5
--- /dev/null
+++ b/tools/cot_dt2c/Makefile
@@ -0,0 +1,68 @@
+#
+# 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
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/LICENSE
@@ -0,0 +1,202 @@
+
+                                 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/__init__.py b/tools/cot_dt2c/cot_dt2c/__init__.py
new file mode 100644
index 0000000..621c55a
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/__init__.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+# type: ignore[attr-defined]
+
+#
+# Copyright (c) 2024, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import sys
+
+if sys.version_info >= (3, 8):
+    from importlib import metadata as importlib_metadata
+else:
+    import importlib_metadata
+
+
+def get_version() -> str:
+    try:
+        return importlib_metadata.version(__name__)
+    except importlib_metadata.PackageNotFoundError:  # pragma: no cover
+        return "unknown"
+
+
+version: str = get_version()
diff --git a/tools/cot_dt2c/cot_dt2c/__main__.py b/tools/cot_dt2c/cot_dt2c/__main__.py
new file mode 100644
index 0000000..5aa4a92
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/__main__.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+# type: ignore[attr-defined]
+#
+# Copyright (c) 2024, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+from cot_dt2c.cli import cli
+if __name__ == "__main__":
+    cli()
diff --git a/tools/cot_dt2c/cot_dt2c/cli.py b/tools/cot_dt2c/cot_dt2c/cli.py
new file mode 100644
index 0000000..d338430
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/cli.py
@@ -0,0 +1,39 @@
+#
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+from pathlib import Path
+from cot_dt2c.cot_dt2c import generateMain
+from cot_dt2c.cot_dt2c import validateMain
+from cot_dt2c.cot_dt2c import visualizeMain
+from cot_dt2c.dt_validator import dtValidatorMain
+
+import click
+
+@click.group()
+@click.version_option()
+def cli():
+    pass
+
+@cli.command()
+@click.argument("inputfile", type=click.Path(dir_okay=True))
+@click.argument("outputfile", type=click.Path(dir_okay=True))
+def convert_to_c(inputfile, outputfile):
+    generateMain(inputfile, outputfile)
+
+@cli.command()
+@click.argument("inputfile", type=click.Path(dir_okay=True))
+def validate_cot(inputfile):
+    validateMain(inputfile)
+
+@cli.command()
+@click.argument("inputfile", type=click.Path(dir_okay=True))
+def visualize_cot(inputfile):
+    visualizeMain(inputfile)
+
+@cli.command()
+@click.argument("inputfiledir", type=click.Path(dir_okay=True))
+def validate_dt(inputfiledir):
+    dtValidatorMain(inputfiledir)
diff --git a/tools/cot_dt2c/cot_dt2c/cot_dt2c.py b/tools/cot_dt2c/cot_dt2c/cot_dt2c.py
new file mode 100644
index 0000000..4056aac
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/cot_dt2c.py
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import sys
+from cot_dt2c.cot_parser import COT
+
+def generateMain(input, output=None):
+    cot = COT(input, output)
+    cot.generate_c_file()
+
+def validateMain(input):
+    cot = COT(input)
+    if not cot.validate_nodes():
+        print("not a valid CoT DT file")
+
+def visualizeMain(input):
+    cot = COT(input)
+    cot.tree_visualization()
+
+if __name__=="__main__":
+    if (len(sys.argv) < 2):
+        print("usage: python3 " + sys.argv[0] + " [dtsi file path] [optional output c file path]")
+        exit()
+    if len(sys.argv) == 3:
+        generateMain(sys.argv[1], sys.argv[2])
+    if len(sys.argv) == 2:
+        validateMain(sys.argv[1])
diff --git a/tools/cot_dt2c/cot_dt2c/cot_parser.py b/tools/cot_dt2c/cot_dt2c/cot_parser.py
new file mode 100644
index 0000000..c1d53e2
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/cot_parser.py
@@ -0,0 +1,804 @@
+#
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+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 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
+
+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)
+        except:
+            print("not a valid CoT DT file")
+            exit(1)
+
+        self.output = outputfile
+        self.input = inputfile
+        self.has_root = False
+
+        # edge cases
+        certs = self.get_all_certificates()
+        for c in certs:
+            if self.if_root(c):
+                if not c.get_fields("signing-key"):
+                    c.properties.append(Property("signing-key", CellArray([LabelReference("subject_pk")])))
+
+    def print_cert_info(self, node:Node):
+        img_id = node.get_field("image-id").values[0].replace('"', "")
+        sign_key = self.get_sign_key(node)
+        nv = self.get_nv_ctr(node)
+
+        info = "<b>name:</b> {}<br><b>image-id:</b> {}<br>{}{}{}"\
+                .format(node.name, img_id, "<b>root-certificate</b><br>" if self.if_root(node) else "", \
+                     "<b>signing-key:</b> " + self.extract_label(sign_key) + "<br>" if sign_key else "", \
+                     "<b>nv counter:</b> " + self.extract_label(nv) + "<br>" if nv else "")
+        return info
+
+    def print_data_info(self, node:Node):
+        oid = node.get_field("oid")
+        info = "<b>name:</b> {}<br><b>oid:</b> {}<br>" \
+                .format(node.name, oid)
+
+        return info
+
+    def print_img_info(self, node:Node):
+        hash = self.extract_label(node.get_fields("hash"))
+        img_id = node.get_field("image-id").values[0].replace('"', "")
+        info = "<b>name:</b> {}<br><b>image-id:</b> {}<br><b>hash:</b> {}"\
+                .format(node.name, img_id, hash)
+
+        return info
+
+    def tree_width(self, parent_set, root):
+        ans = 1
+        stack = [root]
+
+        while stack:
+            tmp_stack = []
+            while stack:
+                cur_node = stack.pop()
+                child = parent_set[cur_node]
+                for c in child:
+                    tmp_stack.append(c)
+
+            stack = tmp_stack.copy()
+            ans = max(ans, len(tmp_stack))
+
+        return ans
+
+    def resolve_lay(self, parent_set, lay, name_idx, root, bounds, break_name):
+        child = parent_set[root]
+
+        if len(child) == 0:
+            return
+
+        width = []
+        total_width = 0
+        for c in child:
+            w = self.tree_width(parent_set, c)
+            width.append(w)
+            total_width += w
+
+        allow_width = bounds[1] - bounds[0]
+        interval = allow_width / total_width
+        start = bounds[0]
+        for i, c in enumerate(child):
+            end = start + interval * width[i]
+            new_bounds = [start, end]
+            lay[name_idx[c]][0] = start + (end - start) / 2
+            if end - start < 0.28:
+                break_name.add(c)
+            start = end
+            self.resolve_lay(parent_set, lay, name_idx, c, new_bounds, break_name)
+
+    def tree_visualization(self):
+        import igraph
+        from igraph import Graph, EdgeSeq
+        import collections
+
+        cert = self.get_certificates()
+        pk = self.get_rot_keys()
+        nv = self.get_nv_counters()
+        image = self.get_images()
+
+        certs = cert.children
+        if pk:
+            pks = pk.children
+        else:
+            pks = []
+        nvs = nv.children
+        images = image.children
+
+        root_name = "CoT"
+
+        G = Graph()
+        detail = []
+        lay = []
+        name_idx = {}
+        parent_set = collections.defaultdict(list)
+
+        G.add_vertex(root_name)
+        detail.append("CoT Root")
+        name_idx[root_name] = len(lay)
+        lay.append([0,0])
+
+        G.add_vertex(cert.name)
+        G.add_edge(root_name, cert.name)
+        detail.append("All Certificates")
+        name_idx[cert.name] = len(lay)
+        lay.append([0, 1])
+        parent_set[root_name].append(cert.name)
+
+        if pk:
+            G.add_vertex(pk.name)
+            detail.append("All Public Trusted Key")
+            G.add_edge(root_name, pk.name)
+            name_idx[pk.name] = len(lay)
+            lay.append([-2.0, 1])
+            parent_set[root_name].append(pk.name)
+
+        G.add_vertex(nv.name)
+        detail.append("All NV Counters")
+        G.add_edge(root_name, nv.name)
+        name_idx[nv.name] = len(lay)
+        lay.append([2.0, 1])
+        parent_set[root_name].append(nv.name)
+
+        if pks:
+            for i, p in enumerate(pks):
+                G.add_vertex(p.name)
+                detail.append(self.print_data_info(p))
+                G.add_edge(pk.name, p.name)
+                name_idx[p.name] = len(lay)
+                parent_set[pk.name].append(p.name)
+                lay.append([0, lay[name_idx[pk.name]][1] + 1])
+
+        for c in certs:
+            G.add_vertex(c.name)
+            detail.append(self.print_cert_info(c))
+            name_idx[c.name] = len(lay)
+            if self.if_root(c):
+                G.add_edge(cert.name, c.name)
+                parent_set[cert.name].append(c.name)
+                lay.append([0, 2])
+            else:
+                parent = self.extract_label(c.get_fields("parent"))
+                G.add_edge(parent, c.name)
+                parent_set[parent].append(c.name)
+                lay.append([0, lay[name_idx[parent]][1] + 1])
+
+        for idx, i in enumerate(images):
+            G.add_vertex(i.name)
+            detail.append(self.print_img_info(i))
+            parent = self.extract_label(i.get_fields("parent"))
+            G.add_edge(parent, i.name)
+            parent_set[parent].append(i.name)
+            name_idx[i.name] = len(lay)
+            lay.append([0, lay[name_idx[parent]][1] + 1])
+
+        for i, n in enumerate(nvs):
+            G.add_vertex(n.name)
+            detail.append(self.print_data_info(n))
+            G.add_edge(nv.name, n.name)
+            name_idx[n.name] = len(lay)
+            parent_set[nv.name].append(n.name)
+            lay.append([0, lay[name_idx[nv.name]][1] + 1])
+
+        break_name = set()
+        self.resolve_lay(parent_set, lay, name_idx, root_name, [-3, 3], break_name)
+        #lay = G.layout('rt')
+
+        numVertex = len(G.get_vertex_dataframe())
+        vertices = G.get_vertex_dataframe()
+        v_label = []
+
+        for i in vertices['name']:
+            if i in break_name and len(i) > 10:
+                middle = len(i) // 2
+                v_label.append(i[:middle] + "<br>" + i[middle:])
+            else:
+                v_label.append(i)
+
+        position = {k: lay[k] for k in range(numVertex)}
+        Y = [lay[k][1] for k in range(numVertex)]
+        M = max(Y)
+
+        es = EdgeSeq(G) # sequence of edges
+        E = [e.tuple for e in G.es] # list of edges
+
+        L = len(position)
+        Xn = [position[k][0] for k in range(L)]
+        Yn = [2*M-position[k][1] for k in range(L)]
+        Xe = []
+        Ye = []
+        for edge in E:
+            Xe += [position[edge[0]][0], position[edge[1]][0], None]
+            Ye += [2*M-position[edge[0]][1], 2*M-position[edge[1]][1], None]
+
+        labels = v_label
+
+        import plotly.graph_objects as go
+        fig = go.Figure()
+        fig.add_trace(go.Scatter(x = Xe,
+                        y = Ye,
+                        mode = 'lines',
+                        line = dict(color='rgb(210,210,210)', width=2),
+                        hoverinfo = 'none'
+                        ))
+        fig.add_trace(go.Scatter(x = Xn,
+                        y = Yn,
+                        mode = 'markers',
+                        name = 'detail',
+                        marker = dict(symbol = 'circle-dot',
+                                        size = 50,
+                                        color = 'rgba(135, 206, 250, 0.8)',    #'#DB4551',
+                                        line = dict(color='MediumPurple', width=3)
+                                        ),
+                        text=detail,
+                        hoverinfo='text',
+                        hovertemplate =
+                            '<b>Detail</b><br>'
+                            '%{text}',
+                        opacity=0.8
+                        ))
+
+        def make_annotations(pos, text, font_size=10, font_color='rgb(0,0,0)'):
+            L = len(pos)
+            if len(text) != L:
+                raise ValueError('The lists pos and text must have the same len')
+            annotations = []
+            for k in range(L):
+                annotations.append(
+                    dict(
+                        text = labels[k],
+                        x = pos[k][0], y = 2*M-position[k][1],
+                        xref = 'x1', yref = 'y1',
+                        font = dict(color = font_color, size = font_size),
+                        showarrow = False)
+                )
+            return annotations
+
+        axis = dict(showline=False, # hide axis line, grid, ticklabels and  title
+            zeroline=False,
+            showgrid=False,
+            showticklabels=False,
+            )
+
+        fig.update_layout(title= 'CoT Device Tree',
+                    annotations=make_annotations(position, v_label),
+                    font_size=12,
+                    showlegend=False,
+                    xaxis=axis,
+                    yaxis=axis,
+                    margin=dict(l=40, r=40, b=85, t=100),
+                    hovermode='closest',
+                    plot_bgcolor='rgb(248,248,248)'
+                    )
+
+        fig.show()
+
+        return
+
+    def if_root(self, node:Node) -> bool:
+        for p in node.properties:
+            if p.name == "root-certificate":
+                return True
+        return False
+
+    def get_sign_key(self, node:Node):
+        for p in node.properties:
+            if p.name == "signing-key":
+                return p.values
+
+        return None
+
+    def get_nv_ctr(self, node:Node):
+        for nv in node.properties:
+            if nv.name == "antirollback-counter":
+                return nv.values
+
+        return None
+
+    def extract_label(self, label) -> str:
+        if not label:
+            return label
+        return label[0].label.name
+
+    def get_auth_data(self, node:Node):
+        return node.children
+
+    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"
+
+        # edge case
+        if not self.if_root(cert) and "key_cert" in cert.name:
+            if "content_pk" in ptr:
+                ptr = "content_pk_buf"
+
+        return type_desc, ptr, len
+
+    def get_node(self, nodes: list[Node], name: str) -> Node:
+        for i in nodes:
+            if i.name == name:
+                return i
+
+    def get_certificates(self) -> Node:
+        children = self.tree.children
+        for i in children:
+            if i.name == "cot":
+                return self.get_node(i.children, "manifests")
+
+    def get_images(self)-> Node:
+        children = self.tree.children
+        for i in children:
+            if i.name == "cot":
+                return self.get_node(i.children, "images")
+
+    def get_nv_counters(self) -> Node:
+        children = self.tree.children
+        return self.get_node(children, "non_volatile_counters")
+
+    def get_rot_keys(self) -> Node:
+        children = self.tree.children
+        return self.get_node(children, "rot_keys")
+
+    def get_all_certificates(self) -> Node:
+        cert = self.get_certificates()
+        return cert.children
+
+    def get_all_images(self) -> Node:
+        image = self.get_images()
+        return image.children
+
+    def get_all_nv_counters(self) -> Node:
+        nv = self.get_nv_counters()
+        return nv.children
+
+    def get_all_pks(self) -> Node:
+        pk = self.get_rot_keys()
+        if not pk:
+            return []
+        return pk.children
+
+    def validate_cert(self, node:Node) -> bool:
+        valid = True
+        if not node.has_field("image-id"):
+            print("{} missing mandatory attribute image-id".format(node.name))
+            valid = False
+
+        if not node.has_field("root-certificate"):
+            if not node.has_field("parent"):
+                print("{} missing mandatory attribute parent".format(node.name))
+                valid = False
+            else:
+                # check if refer to non existing parent
+                certs = self.get_all_certificates()
+                found = False
+                for c in certs:
+                    if c.name == self.extract_label(node.get_fields("parent")):
+                        found = True
+
+                if not found:
+                    print("{} refer to non existing parent".format(node.name))
+                    valid = False
+
+        else:
+            self.has_root = True
+
+        child = node.children
+        if child:
+            for c in child:
+                if not c.has_field("oid"):
+                    print("{} missing mandatory attribute oid".format(c.name))
+                    valid = False
+
+        return valid
+
+    def validate_img(self, node:Node) -> bool:
+        valid = True
+        if not node.has_field("image-id"):
+            print("{} missing mandatory attribute image-id".format(node.name))
+            valid = False
+
+        if not node.has_field("parent"):
+            print("{} missing mandatory attribute parent".format(node.name))
+            valid = False
+
+        if not node.has_field("hash"):
+            print("{} missing mandatory attribute hash".format(node.name))
+            valid = False
+
+        # check if refer to non existing parent
+        certs = self.get_all_certificates()
+        found = False
+        for c in certs:
+            if c.name == self.extract_label(node.get_fields("parent")):
+                found = True
+
+        if not found:
+            print("{} refer to non existing parent".format(node.name))
+            valid = False
+
+        return valid
+
+    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()
+
+        for n in certs:
+            node_valid = self.validate_cert(n)
+            valid = valid and node_valid
+
+        for i in images:
+            node_valid = self.validate_img(i)
+            valid = valid and node_valid
+
+        if not self.has_root:
+            print("missing root certificate")
+
+        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):
+        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 all_cert_to_c(self, f):
+        certs = self.get_all_certificates()
+        for c in certs:
+            self.cert_to_c(c, f)
+
+        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))
+
+        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("\t.img_type = IMG_CERT,\n")
+
+        if not self.if_root(node):
+            f.write("\t.parent = &{},\n".format(node.get_field("parent").label.name))
+        else:
+            f.write("\t.parent = NULL,\n")
+
+        sign = self.get_sign_key(node)
+        nv_ctr = self.get_nv_ctr(node)
+
+        if sign or nv_ctr:
+            f.write("\t.img_auth_methods = (const auth_method_desc_t[AUTH_METHOD_NUM]) {\n")
+
+        if sign:
+            f.write("\t\t[0] = {\n")
+            f.write("\t\t\t.type = AUTH_METHOD_SIG,\n")
+            f.write("\t\t\t.param.sig = {\n")
+
+            f.write("\t\t\t\t.pk = &{},\n".format(self.extract_label(sign)))
+            f.write("\t\t\t\t.sig = &sig,\n")
+            f.write("\t\t\t\t.alg = &sig_alg,\n")
+            f.write("\t\t\t\t.data = &raw_data\n")
+            f.write("\t\t\t}\n")
+            f.write("\t\t}}{}\n".format("," if nv_ctr else ""))
+
+        if nv_ctr:
+            f.write("\t\t[1] = {\n")
+            f.write("\t\t\t.type = AUTH_METHOD_NV_CTR,\n")
+            f.write("\t\t\t.param.nv_ctr = {\n")
+
+            f.write("\t\t\t\t.cert_nv_ctr = &{},\n".format(self.extract_label(nv_ctr)))
+            f.write("\t\t\t\t.plat_nv_ctr = &{}\n".format(self.extract_label(nv_ctr)))
+
+            f.write("\t\t\t}\n")
+            f.write("\t\t}\n")
+
+        f.write("\t},\n")
+
+        auth_data = self.get_auth_data(node)
+        if auth_data:
+            f.write("\t.authenticated_data = (const auth_param_desc_t[COT_MAX_VERIFIED_PARAMS]) {\n")
+
+            for i, d in enumerate(auth_data):
+                type_desc, ptr, data_len = self.format_auth_data_val(d, node)
+
+                f.write("\t\t[{}] = {{\n".format(i))
+                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.len = {}\n".format(data_len))
+                f.write("\t\t\t}\n")
+
+                f.write("\t\t}}{}\n".format("," if i != len(auth_data) - 1 else ""))
+
+            f.write("\t}\n")
+
+        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))
+
+        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("\t.img_type = IMG_RAW,\n")
+        f.write("\t.parent = &{},\n".format(node.get_field("parent").label.name))
+        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("\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):
+        images = self.get_all_images()
+        for i in images:
+            self.img_to_c(i, f)
+
+        f.write("\n")
+
+    def nv_to_c(self, f):
+        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")))
+
+        f.write("\n")
+
+        return
+
+    def pk_to_c(self, f):
+        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")))
+
+        f.write("\n")
+        return
+
+    def buf_to_c(self, f):
+        certs = self.get_all_certificates()
+
+        buffers = {}
+
+        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")
+
+        f.write("\n")
+
+    def param_to_c(self, f):
+        f.write("static auth_param_type_desc_t subject_pk = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_PUB_KEY, 0);\n")
+        f.write("static auth_param_type_desc_t sig = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_SIG, 0);\n")
+        f.write("static auth_param_type_desc_t sig_alg = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_SIG_ALG, 0);\n")
+        f.write("static auth_param_type_desc_t raw_data = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_RAW_DATA, 0);\n")
+        f.write("\n")
+
+        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 ifdef:
+                for i in ifdef:
+                    f.write("#endif\n")
+
+        f.write("\n")
+
+    def cot_to_c(self, f):
+        certs = self.get_all_certificates()
+        images = self.get_all_images()
+
+        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))
+
+            f.write("\t[{}]	=	&{}{}\n".format(c.get_field("image-id").values[0], c.name, ","))
+
+            if ifdef:
+                for i in ifdef:
+                    f.write("#endif\n")
+
+        for i, c in enumerate(images):
+            ifdef = c.get_fields("ifdef")
+            if ifdef:
+                for i in ifdef:
+                    f.write("{}\n".format(i))
+
+            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("};\n\n")
+        f.write("REGISTER_COT(cot_desc);\n")
+        return
+
+    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)
+
+        return
diff --git a/tools/cot_dt2c/cot_dt2c/dt_validator.py b/tools/cot_dt2c/cot_dt2c/dt_validator.py
new file mode 100644
index 0000000..65e8ca2
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/dt_validator.py
@@ -0,0 +1,130 @@
+#
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import sys
+from os import path, walk, mkdir
+import subprocess
+from cot_dt2c.pydevicetree import *
+
+class bcolors:
+    HEADER = '\033[95m'
+    OKBLUE = '\033[94m'
+    OKCYAN = '\033[96m'
+    OKGREEN = '\033[92m'
+    WARNING = '\033[93m'
+    FAIL = '\033[91m'
+    ENDC = '\033[0m'
+    BOLD = '\033[1m'
+    UNDERLINE = '\033[4m'
+
+class DTTree:
+    def __init__(self, input):
+        self.input = input
+        self.test_dir = "./tmp"
+        self.logging_file = self.test_dir + "/result.log"
+
+    def dtValidate(self):
+        subprocess.run(["rm", "-rf", self.test_dir])
+
+        if not path.exists(self.test_dir):
+            mkdir(self.test_dir)
+
+        if path.isfile(self.input):
+            self.dtValidateFile(self.input, printInfo=True)
+            return
+
+        if path.isdir(self.input):
+            self.dtValidateFiles()
+            return
+
+    def dtValidateFile(self, input, printInfo=False):
+        valid, tree = self.dtParseFile(input, printInfo)
+
+        if not valid:
+            return False
+
+        if input.rfind("/") != -1:
+            filename = self.test_dir + input[input.rfind("/"):]
+        else:
+            filename = self.test_dir + "/" + input
+
+        f = open(filename, "w+")
+        if "/dts-v1/;" not in str(tree):
+            f.write("/dts-v1/;\n\n")
+        f.write(str(tree))
+        f.close()
+
+        if str(tree) == "":
+            return valid
+
+        return valid
+
+    def dtParseFile(self, input, printInfo=False):
+        with open(input, 'r') as f:
+            contents = f.read()
+
+        pos = contents.find("/ {")
+        if pos != -1:
+            contents = contents[pos:]
+
+        try:
+            tree = Devicetree.parseStr(contents)
+            if printInfo:
+                print(bcolors.OKGREEN + "{} parse tree successfully".format(input) + bcolors.ENDC)
+        except Exception as e:
+            if printInfo:
+                print(bcolors.FAIL + "{} parse tree failed:\t{}".format(input, str(e)) + bcolors.ENDC)
+            else:
+                f = open(self.logging_file, "a")
+                f.write("=====================================================================================\n")
+                f.write("{} result:\n".format(input))
+                f.write("{} INVALID:\t{}\n".format(input, str(e)))
+                f.close()
+            return False, None
+
+        return True, tree
+
+    def dtValidateFiles(self):
+        f = []
+        for (dirpath, dirnames, filenames) in walk(self.input):
+            f.extend(filenames)
+
+        allFile = len(f)
+        dtsiFile = 0
+        validFile = 0
+        invalidFile = 0
+
+        for i in f:
+            if (".dtsi" in i or ".dts" in i) and "cot" not in i and "fw-config" not in i:
+                dtsiFile += 1
+                valid = True
+
+                if self.input[-1] == "/":
+                    valid = self.dtValidateFile(self.input + i)
+                else:
+                    valid = self.dtValidateFile(self.input + "/" + i)
+
+                if valid:
+                    validFile += 1
+                else:
+                    invalidFile += 1
+
+        print("=====================================================")
+        print("Total File: " + str(allFile))
+        print("Total DT File: " + str(dtsiFile))
+        print("Total Valid File: " + str(validFile))
+        print("Total Invalid File: " + str(invalidFile))
+
+def dtValidatorMain(input):
+    dt = DTTree(input)
+    dt.dtValidate()
+
+if __name__=="__main__":
+    if (len(sys.argv) < 2):
+        print("usage: python3 " + sys.argv[0] + " [dtsi file path] or [dtsi folder path]")
+        exit()
+    if len(sys.argv) == 2:
+        dtValidatorMain(sys.argv[1])
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/__init__.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/__init__.py
new file mode 100644
index 0000000..49595a7
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/__init__.py
@@ -0,0 +1,5 @@
+#!/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
new file mode 100644
index 0000000..f30d897
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/__init__.py
@@ -0,0 +1,9 @@
+#!/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
new file mode 100644
index 0000000..fdd6f0e
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/directive.py
@@ -0,0 +1,46 @@
+#!/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
new file mode 100644
index 0000000..30c54dc
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/helpers.py
@@ -0,0 +1,28 @@
+#!/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
new file mode 100644
index 0000000..d203af8
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/node.py
@@ -0,0 +1,514 @@
+#!/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
new file mode 100644
index 0000000..d5fb687
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/property.py
@@ -0,0 +1,278 @@
+#!/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
new file mode 100644
index 0000000..54b2d28
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/reference.py
@@ -0,0 +1,111 @@
+#!/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
new file mode 100644
index 0000000..96768b3
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/__init__.py
@@ -0,0 +1,5 @@
+#!/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
new file mode 100644
index 0000000..fb165e1
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/grammar.py
@@ -0,0 +1,95 @@
+#!/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
new file mode 100644
index 0000000..0692482
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/parser.py
@@ -0,0 +1,238 @@
+#!/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/pyproject.toml b/tools/cot_dt2c/pyproject.toml
new file mode 100644
index 0000000..d383924
--- /dev/null
+++ b/tools/cot_dt2c/pyproject.toml
@@ -0,0 +1,60 @@
+# Poetry pyproject.toml: https://python-poetry.org/docs/pyproject/
+[build-system]
+requires = ["poetry_core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.poetry]
+name = "cot_dt2c"
+version = "0.1.0"
+description = "CoT-dt2c Tool is a python script to convert CoT DT file into corresponding C file"
+authors = ["Arm Ltd <tf-a@lists.trustedfirmware.org>"]
+license = "BSD-3"
+repository = "https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git/"
+homepage = "https://trustedfirmware-a.readthedocs.io/en/latest/index.html"
+
+# Pypi classifiers: https://pypi.org/classifiers/
+classifiers = [
+  "Development Status :: 3 - Alpha",
+  "Intended Audience :: Developers",
+  "Operating System :: OS Independent",
+  "Topic :: Software Development :: Libraries :: Python Modules",
+  "License :: OSI Approved :: BSD License",
+  "Programming Language :: Python :: 3",
+  "Programming Language :: Python :: 3.8",
+  "Programming Language :: Python :: 3.9",
+]
+
+
+[tool.poetry.dependencies]
+python = "^3.8"
+click = "^8.1.7"
+pyparsing = "^2.4.7"
+plotly = "^5.23.0"
+pandas = "^2.2.2"
+igraph = "^0.11.6"
+
+[tool.poetry.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
+pretty = true
+show_traceback = true
+color_output = true
+
+[tool.coverage.run]
+source = ["tests"]
+
+[coverage.paths]
+source = "cot_dt2c"
+
+[tool.poetry.scripts]
+# Entry points for the package https://python-poetry.org/docs/pyproject/#scripts
+# "cot-dt2c" = "cot_dt2c.__main__:cli"
+"cot-dt2c" = "cot_dt2c.__main__:cli"
diff --git a/tools/cot_dt2c/requirements.txt b/tools/cot_dt2c/requirements.txt
new file mode 100644
index 0000000..246b81d
--- /dev/null
+++ b/tools/cot_dt2c/requirements.txt
@@ -0,0 +1,6 @@
+mypy
+pylint
+pyparsing
+igraph
+pandas
+plotly
diff --git a/tools/cot_dt2c/tests/test.dtsi b/tools/cot_dt2c/tests/test.dtsi
new file mode 100644
index 0000000..ee744e6
--- /dev/null
+++ b/tools/cot_dt2c/tests/test.dtsi
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a valid CoT DT file
+ *
+ */
+
+#include <example/example.h>
+#include <example/example/example.h>
+
+cot {
+	manifests {
+		compatible = "arm, cert-descs";
+
+#if defined(test)
+		example_cert: example_cert {
+			root-certificate;
+			image-id =<EXAMPLE_ID>;
+			antirollback-counter = <&example_ctr>;
+
+			example_hash: example_hash
+			{
+				oid = EXAMPLE_HASH_ID;
+			};
+		};
+#endif
+	};
+
+	images {
+		compatible = "arm, img-descs";
+
+		example {
+			image-id = <EXAMPLE_ID>;
+			parent = <&example_cert>;
+			hash = <&example_hash>;
+		};
+	};
+};
+
+non_volatile_counters: non_volatile_counters {
+	compatible = "arm, non-volatile-counter";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	example_ctr: example_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = CCA_FW_NVCOUNTER_OID;
+	};
+};
+
+rot_keys {
+	example_pk: example_pk {
+		oid = EXAMPLE_PK_OID;
+	};
+};
diff --git a/tools/cot_dt2c/tests/test2.dtsi b/tools/cot_dt2c/tests/test2.dtsi
new file mode 100644
index 0000000..c4dbf83
--- /dev/null
+++ b/tools/cot_dt2c/tests/test2.dtsi
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a valid CoT DT file
+ *
+ */
+
+#if test
+#include <example/example.h>
+#include <example/example/example.h>
+#endif
+
+cot
+{
+	manifests
+	{
+		compatible = "arm, cert-descs";
+#if defined (test)
+		example_cert: example_cert
+		{
+			root-certificate;
+			image-id =<EXAMPLE_ID>;
+			antirollback-counter = <&example_ctr>;
+
+			example_hash: example_hash
+			{
+				oid = EXAMPLE_HASH_ID;
+			};
+
+		};
+#endif
+	};
+
+	images
+	{
+		compatible = "arm, img-descs";
+
+		example
+		{
+			image-id = <EXAMPLE_ID>;
+			parent = <&example_cert>;
+			hash = <&example_hash>;
+		};
+	};
+};
+
+non_volatile_counters: non_volatile_counters
+{
+	compatible = "arm, non-volatile-counter";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	example_ctr: example_ctr
+	{
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = CCA_FW_NVCOUNTER_OID;
+	};
+};
+
+rot_keys
+{
+	example_pk: example_pk
+	{
+		oid = EXAMPLE_PK_OID;
+	};
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_bracket.dtsi b/tools/cot_dt2c/tests/test_invalid_bracket.dtsi
new file mode 100644
index 0000000..ec5f9c7
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_bracket.dtsi
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there is
+ * unmatching bracket
+ *
+ */
+
+#include <example/example.h>
+#include <example/example/example.h>
+
+cot {
+	manifests {
+		compatible = "arm, cert-descs";
+
+		example_cert: example_cert {
+			root-certificate;
+			image-id =<EXAMPLE_ID>;
+			antirollback-counter = <&example_ctr>;
+
+			example_hash: example_hash
+			{
+				oid = EXAMPLE_HASH_ID;
+			};
+
+		};
+	};
+
+	images {
+		compatible = "arm, img-descs";
+
+		example {
+			image-id = <EXAMPLE_ID>;
+			parent = <&example_cert>;
+			hash = <&example_hash>;
+		};
+};
+
+non_volatile_counters: non_volatile_counters {
+	compatible = "arm, non-volatile-counter";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	example_ctr: example_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = CCA_FW_NVCOUNTER_OID;
+	};
+};
+
+rot_keys {
+	example_pk: example_pk {
+		oid = EXAMPLE_PK_OID;
+	};
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_ifdef.dtsi b/tools/cot_dt2c/tests/test_invalid_ifdef.dtsi
new file mode 100644
index 0000000..5bc6bb9
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_ifdef.dtsi
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there is
+ * unmatching ifdef macro
+ *
+ */
+
+#include <example/example.h>
+#include <example/example/example.h>
+
+cot {
+	manifests {
+		compatible = "arm, cert-descs";
+
+		example_cert: example_cert {
+			root-certificate;
+			image-id =<EXAMPLE_ID>;
+			antirollback-counter = <&example_ctr>;
+
+			example_hash: example_hash
+			{
+				oid = EXAMPLE_HASH_ID;
+			};
+
+		};
+	};
+
+#if defined(test)
+	images {
+		compatible = "arm, img-descs";
+
+		example {
+			image-id = <EXAMPLE_ID>;
+			parent = <&example_cert>;
+			hash = <&example_hash>;
+		};
+	};
+};
+
+non_volatile_counters: non_volatile_counters {
+	compatible = "arm, non-volatile-counter";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	example_ctr: example_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = CCA_FW_NVCOUNTER_OID;
+	};
+};
+
+rot_keys {
+	example_pk: example_pk {
+		oid = EXAMPLE_PK_OID;
+	};
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_ifdef2.dtsi b/tools/cot_dt2c/tests/test_invalid_ifdef2.dtsi
new file mode 100644
index 0000000..c915168
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_ifdef2.dtsi
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there is
+ * unmatching ifdef macro
+ *
+ */
+
+#include <example/example.h>
+#include <example/example/example.h>
+
+cot {
+	manifests {
+		compatible = "arm, cert-descs";
+
+		example_cert: example_cert {
+			root-certificate;
+			image-id =<EXAMPLE_ID>;
+			antirollback-counter = <&example_ctr>;
+
+			example_hash: example_hash
+			{
+				oid = EXAMPLE_HASH_ID;
+			};
+
+		};
+	};
+
+#if defined(test)
+	images {
+		compatible = "arm, img-descs";
+
+		example {
+			image-id = <EXAMPLE_ID>;
+			parent = <&example_cert>;
+			hash = <&example_hash>;
+		};
+	};
+#endif
+#endif
+};
+
+non_volatile_counters: non_volatile_counters {
+	compatible = "arm, non-volatile-counter";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	example_ctr: example_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = CCA_FW_NVCOUNTER_OID;
+	};
+};
+
+rot_keys {
+	example_pk: example_pk {
+		oid = EXAMPLE_PK_OID;
+	};
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_missing_attribute.dtsi b/tools/cot_dt2c/tests/test_invalid_missing_attribute.dtsi
new file mode 100644
index 0000000..9c0a5f2
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_missing_attribute.dtsi
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there
+ * are image/certificate that missing mandantory attributes
+ *
+ */
+
+#include <tools_share/cca_oid.h>
+#include <common/tbbr/tbbr_img_def.h>
+#include <common/nv_cntr_ids.h>
+
+cot {
+	manifests {
+		compatible = "arm, cert-descs";
+
+		cca_content_cert: cca_content_cert {
+			root-certificate;
+			antirollback-counter = <&cca_nv_ctr>;
+
+			tb_fw_hash: tb_fw_hash {
+				oid = TRUSTED_BOOT_FW_HASH_OID;
+			};
+			tb_fw_config_hash: tb_fw_config_hash {
+				oid = TRUSTED_BOOT_FW_CONFIG_HASH_OID;
+			};
+			hw_config_hash: hw_config_hash {
+			};
+			fw_config_hash: fw_config_hash {
+				oid = FW_CONFIG_HASH_OID;
+			};
+			soc_fw_hash: soc_fw_hash {
+				oid = SOC_AP_FW_HASH_OID;
+			};
+			soc_fw_config_hash: soc_fw_config_hash {
+				oid = SOC_FW_CONFIG_HASH_OID;
+			};
+			rmm_hash: rmm_hash {
+				oid = RMM_HASH_OID;
+			};
+		};
+
+		core_swd_key_cert: core_swd_key_cert {
+			root-certificate;
+			image-id = <CORE_SWD_KEY_CERT_ID>;
+			signing-key = <&swd_rot_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			core_swd_pk: core_swd_pk {
+				oid = CORE_SWD_PK_OID;
+			};
+		};
+
+		trusted_os_fw_content_cert: trusted_os_fw_content_cert {
+			image-id = <TRUSTED_OS_FW_CONTENT_CERT_ID>;
+			parent = <&core_swd_key_cert>;
+			signing-key = <&core_swd_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			tos_fw_hash: tos_fw_hash {
+				oid = TRUSTED_OS_FW_HASH_OID;
+			};
+			tos_fw_config_hash: tos_fw_config_hash {
+				oid = TRUSTED_OS_FW_CONFIG_HASH_OID;
+			};
+		};
+
+		plat_key_cert: plat_key_cert {
+			root-certificate;
+			image-id = <PLAT_KEY_CERT_ID>;
+			signing-key = <&prot_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			plat_pk: plat_pk {
+				oid = PLAT_PK_OID;
+			};
+		};
+
+		non_trusted_fw_content_cert: non_trusted_fw_content_cert {
+			image-id = <NON_TRUSTED_FW_CONTENT_CERT_ID>;
+			parent = <&plat_key_cert>;
+			signing-key = <&plat_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			nt_world_bl_hash: nt_world_bl_hash {
+				oid = NON_TRUSTED_WORLD_BOOTLOADER_HASH_OID;
+			};
+			nt_fw_config_hash: nt_fw_config_hash {
+				oid = NON_TRUSTED_FW_CONFIG_HASH_OID;
+			};
+		};
+
+#if defined(SPD_spmd)
+		sip_sp_content_cert: sip_sp_content_cert {
+			image-id = <SIP_SP_CONTENT_CERT_ID>;
+			parent = <&core_swd_key_cert>;
+			signing-key = <&core_swd_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			sp_pkg1_hash: sp_pkg1_hash {
+				oid = SP_PKG1_HASH_OID;
+			};
+			sp_pkg2_hash: sp_pkg2_hash {
+				oid = SP_PKG2_HASH_OID;
+			};
+			sp_pkg3_hash: sp_pkg3_hash {
+				oid = SP_PKG3_HASH_OID;
+			};
+			sp_pkg4_hash: sp_pkg4_hash {
+				oid = SP_PKG4_HASH_OID;
+			};
+		};
+
+		plat_sp_content_cert: plat_sp_content_cert {
+			parent = <&plat_key_cert>;
+			signing-key = <&plat_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			sp_pkg5_hash: sp_pkg5_hash {
+				oid = SP_PKG5_HASH_OID;
+			};
+			sp_pkg6_hash: sp_pkg6_hash {
+				oid = SP_PKG6_HASH_OID;
+			};
+			sp_pkg7_hash: sp_pkg7_hash {
+				oid = SP_PKG7_HASH_OID;
+			};
+			sp_pkg8_hash: sp_pkg8_hash {
+				oid = SP_PKG8_HASH_OID;
+			};
+		};
+#endif
+	};
+
+	images {
+		compatible = "arm, img-descs";
+
+		hw_config {
+			image-id = <HW_CONFIG_ID>;
+			hash = <&hw_config_hash>;
+		};
+
+		bl31_image {
+			image-id = <BL31_IMAGE_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&soc_fw_hash>;
+		};
+
+		soc_fw_config {
+			image-id = <SOC_FW_CONFIG_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&soc_fw_config_hash>;
+		};
+
+		rmm_image {
+			image-id = <RMM_IMAGE_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&rmm_hash>;
+		};
+
+		bl32_image {
+			image-id = <BL32_IMAGE_ID>;
+			parent = <&trusted_os_fw_content_cert>;
+			hash = <&tos_fw_hash>;
+		};
+
+		tos_fw_config {
+			image-id = <TOS_FW_CONFIG_ID>;
+			parent = <&trusted_os_fw_content_cert>;
+			hash = <&tos_fw_config_hash>;
+		};
+
+		bl33_image {
+			image-id = <BL33_IMAGE_ID>;
+			parent = <&non_trusted_fw_content_cert>;
+			hash = <&nt_world_bl_hash>;
+		};
+
+		nt_fw_config {
+			image-id = <NT_FW_CONFIG_ID>;
+			parent = <&non_trusted_fw_content_cert>;
+			hash = <&nt_fw_config_hash>;
+		};
+
+#if defined(SPD_spmd)
+		sp_pkg1 {
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg1_hash>;
+		};
+
+		sp_pkg2 {
+			image-id = <SP_PKG2_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg2_hash>;
+		};
+
+		sp_pkg3 {
+			image-id = <SP_PKG3_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg3_hash>;
+		};
+
+		sp_pkg4 {
+			image-id = <SP_PKG4_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg4_hash>;
+		};
+
+		sp_pkg5 {
+			image-id = <SP_PKG5_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg5_hash>;
+		};
+
+		sp_pkg6 {
+			image-id = <SP_PKG6_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg6_hash>;
+		};
+
+		sp_pkg7 {
+			image-id = <SP_PKG7_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg7_hash>;
+		};
+
+		sp_pkg8 {
+			image-id = <SP_PKG8_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg8_hash>;
+		};
+#endif
+	};
+};
+
+non_volatile_counters: non_volatile_counters {
+	compatible = "arm, non-volatile-counter";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	cca_nv_ctr: cca_nv_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = CCA_FW_NVCOUNTER_OID;
+	};
+
+	trusted_nv_ctr: trusted_nv_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = TRUSTED_FW_NVCOUNTER_OID;
+	};
+
+	non_trusted_nv_ctr: non_trusted_nv_ctr {
+		id  = <NON_TRUSTED_NV_CTR_ID>;
+		oid = NON_TRUSTED_FW_NVCOUNTER_OID;
+	};
+};
+
+rot_keys {
+	swd_rot_pk: swd_rot_pk {
+		oid = SWD_ROT_PK_OID;
+	};
+	prot_pk: prot_pk {
+		oid = PROT_PK_OID;
+	};
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_missing_attribute2.dtsi b/tools/cot_dt2c/tests/test_invalid_missing_attribute2.dtsi
new file mode 100644
index 0000000..01b2597
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_missing_attribute2.dtsi
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there
+ * are image/certificate that points to invalid parent
+ *
+ */
+
+#include <tools_share/cca_oid.h>
+#include <common/tbbr/tbbr_img_def.h>
+#include <common/nv_cntr_ids.h>
+
+cot {
+	manifests {
+		compatible = "arm, cert-descs";
+
+		cca_content_cert: cca_content_cert {
+			root-certificate;
+			image-id =<CCA_CONTENT_CERT_ID>;
+			antirollback-counter = <&cca_nv_ctr>;
+
+			tb_fw_hash: tb_fw_hash {
+				oid = TRUSTED_BOOT_FW_HASH_OID;
+			};
+			tb_fw_config_hash: tb_fw_config_hash {
+				oid = TRUSTED_BOOT_FW_CONFIG_HASH_OID;
+			};
+			hw_config_hash: hw_config_hash {
+				oid = HW_CONFIG_HASH_OID;
+			};
+			fw_config_hash: fw_config_hash {
+				oid = FW_CONFIG_HASH_OID;
+			};
+			soc_fw_hash: soc_fw_hash {
+				oid = SOC_AP_FW_HASH_OID;
+			};
+			soc_fw_config_hash: soc_fw_config_hash {
+				oid = SOC_FW_CONFIG_HASH_OID;
+			};
+			rmm_hash: rmm_hash {
+				oid = RMM_HASH_OID;
+			};
+		};
+
+		core_swd_key_cert: core_swd_key_cert {
+			root-certificate;
+			image-id = <CORE_SWD_KEY_CERT_ID>;
+			signing-key = <&swd_rot_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			core_swd_pk: core_swd_pk {
+				oid = CORE_SWD_PK_OID;
+			};
+		};
+
+		trusted_os_fw_content_cert: trusted_os_fw_content_cert {
+			image-id = <TRUSTED_OS_FW_CONTENT_CERT_ID>;
+			parent = <&core_swd_key_cert>;
+			signing-key = <&core_swd_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			tos_fw_hash: tos_fw_hash {
+				oid = TRUSTED_OS_FW_HASH_OID;
+			};
+			tos_fw_config_hash: tos_fw_config_hash {
+				oid = TRUSTED_OS_FW_CONFIG_HASH_OID;
+			};
+		};
+
+		plat_key_cert: plat_key_cert {
+			root-certificate;
+			image-id = <PLAT_KEY_CERT_ID>;
+			signing-key = <&prot_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			plat_pk: plat_pk {
+				oid = PLAT_PK_OID;
+			};
+		};
+
+		non_trusted_fw_content_cert: non_trusted_fw_content_cert {
+			image-id = <NON_TRUSTED_FW_CONTENT_CERT_ID>;
+			parent = <&cca_content_cert>;
+			signing-key = <&plat_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			nt_world_bl_hash: nt_world_bl_hash {
+				oid = NON_TRUSTED_WORLD_BOOTLOADER_HASH_OID;
+			};
+			nt_fw_config_hash: nt_fw_config_hash {
+				oid = NON_TRUSTED_FW_CONFIG_HASH_OID;
+			};
+		};
+
+#if defined(SPD_spmd)
+		sip_sp_content_cert: sip_sp_content_cert {
+			image-id = <SIP_SP_CONTENT_CERT_ID>;
+			parent = <&cca_content_cert>;
+			signing-key = <&core_swd_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			sp_pkg1_hash: sp_pkg1_hash {
+				oid = SP_PKG1_HASH_OID;
+			};
+			sp_pkg2_hash: sp_pkg2_hash {
+				oid = SP_PKG2_HASH_OID;
+			};
+			sp_pkg3_hash: sp_pkg3_hash {
+				oid = SP_PKG3_HASH_OID;
+			};
+			sp_pkg4_hash: sp_pkg4_hash {
+				oid = SP_PKG4_HASH_OID;
+			};
+		};
+
+		plat_sp_content_cert: plat_sp_content_cert {
+			image-id = <PLAT_SP_CONTENT_CERT_ID>;
+			signing-key = <&plat_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			sp_pkg5_hash: sp_pkg5_hash {
+				oid = SP_PKG5_HASH_OID;
+			};
+			sp_pkg6_hash: sp_pkg6_hash {
+				oid = SP_PKG6_HASH_OID;
+			};
+			sp_pkg7_hash: sp_pkg7_hash {
+				oid = SP_PKG7_HASH_OID;
+			};
+			sp_pkg8_hash: sp_pkg8_hash {
+				oid = SP_PKG8_HASH_OID;
+			};
+		};
+#endif
+	};
+
+	images {
+		compatible = "arm, img-descs";
+
+		hw_config {
+			image-id = <HW_CONFIG_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&hw_config_hash>;
+		};
+
+		bl31_image {
+			image-id = <BL31_IMAGE_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&soc_fw_hash>;
+		};
+
+		soc_fw_config {
+			image-id = <SOC_FW_CONFIG_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&soc_fw_config_hash>;
+		};
+
+		rmm_image {
+			image-id = <RMM_IMAGE_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&rmm_hash>;
+		};
+
+		bl32_image {
+			image-id = <BL32_IMAGE_ID>;
+			parent = <&trusted_os_fw_content_cert>;
+			hash = <&tos_fw_hash>;
+		};
+
+		tos_fw_config {
+			image-id = <TOS_FW_CONFIG_ID>;
+			parent = <&trusted_os_fw_content_cert>;
+			hash = <&tos_fw_config_hash>;
+		};
+
+		bl33_image {
+			image-id = <BL33_IMAGE_ID>;
+			parent = <&non_trusted_fw_content_cert>;
+			hash = <&nt_world_bl_hash>;
+		};
+
+		nt_fw_config {
+			image-id = <NT_FW_CONFIG_ID>;
+			hash = <&nt_fw_config_hash>;
+		};
+
+#if defined(SPD_spmd)
+		sp_pkg1 {
+			image-id = <SP_PKG1_ID>;
+			hash = <&sp_pkg1_hash>;
+		};
+
+		sp_pkg2 {
+			image-id = <SP_PKG2_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg2_hash>;
+		};
+
+		sp_pkg3 {
+			image-id = <SP_PKG3_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg3_hash>;
+		};
+
+		sp_pkg4 {
+			image-id = <SP_PKG4_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg4_hash>;
+		};
+
+		sp_pkg5 {
+			image-id = <SP_PKG5_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg5_hash>;
+		};
+
+		sp_pkg6 {
+			image-id = <SP_PKG6_ID>;
+			parent = <&wrong_parent>;
+			hash = <&sp_pkg6_hash>;
+		};
+
+		sp_pkg7 {
+			image-id = <SP_PKG7_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg7_hash>;
+		};
+
+		sp_pkg8 {
+			image-id = <SP_PKG8_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg8_hash>;
+		};
+#endif
+	};
+};
+
+non_volatile_counters: non_volatile_counters {
+	compatible = "arm, non-volatile-counter";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	cca_nv_ctr: cca_nv_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = CCA_FW_NVCOUNTER_OID;
+	};
+
+	trusted_nv_ctr: trusted_nv_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = TRUSTED_FW_NVCOUNTER_OID;
+	};
+
+	non_trusted_nv_ctr: non_trusted_nv_ctr {
+		id  = <NON_TRUSTED_NV_CTR_ID>;
+		oid = NON_TRUSTED_FW_NVCOUNTER_OID;
+	};
+};
+
+rot_keys {
+	swd_rot_pk: swd_rot_pk {
+		oid = SWD_ROT_PK_OID;
+	};
+	prot_pk: prot_pk {
+		oid = PROT_PK_OID;
+	};
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_missing_ctr.dtsi b/tools/cot_dt2c/tests/test_invalid_missing_ctr.dtsi
new file mode 100644
index 0000000..5958f5d
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_missing_ctr.dtsi
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there
+ * are image/certificate that missing definition of
+ * nv counters
+ *
+ */
+
+#include <example/example.h>
+#include <example/example/example.h>
+
+cot {
+	manifests {
+		compatible = "arm, cert-descs";
+
+		example_cert: example_cert {
+			root-certificate;
+			image-id =<EXAMPLE_ID>;
+			signing-key = <&swd_rot_pk>;
+			antirollback-counter = <&example_ctr>;
+
+			example_hash: example_hash
+			{
+				oid = EXAMPLE_HASH_ID;
+			};
+
+		};
+	};
+
+	images {
+		compatible = "arm, img-descs";
+
+		example {
+			image-id = <EXAMPLE_ID>;
+			parent = <&example_cert>;
+			hash = <&example_hash>;
+		};
+	};
+};
+
+non_volatile_counters: non_volatile_counters {
+	compatible = "arm, non-volatile-counter";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+};
+
+rot_keys {
+	example_pk: example_pk {
+		oid = EXAMPLE_PK_OID;
+	};
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_missing_root.dtsi b/tools/cot_dt2c/tests/test_invalid_missing_root.dtsi
new file mode 100644
index 0000000..465a4c6
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_missing_root.dtsi
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there
+ * are image/certificate that missing root certificate
+ *
+ */
+
+#include <tools_share/cca_oid.h>
+#include <common/tbbr/tbbr_img_def.h>
+#include <common/nv_cntr_ids.h>
+
+cot {
+	manifests {
+		compatible = "arm, cert-descs";
+
+		core_swd_key_cert: core_swd_key_cert {
+			image-id = <CORE_SWD_KEY_CERT_ID>;
+			signing-key = <&swd_rot_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			core_swd_pk: core_swd_pk {
+				oid = CORE_SWD_PK_OID;
+			};
+		};
+
+		trusted_os_fw_content_cert: trusted_os_fw_content_cert {
+			image-id = <TRUSTED_OS_FW_CONTENT_CERT_ID>;
+			parent = <&core_swd_key_cert>;
+			signing-key = <&core_swd_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			tos_fw_hash: tos_fw_hash {
+				oid = TRUSTED_OS_FW_HASH_OID;
+			};
+			tos_fw_config_hash: tos_fw_config_hash {
+				oid = TRUSTED_OS_FW_CONFIG_HASH_OID;
+			};
+		};
+
+		plat_key_cert: plat_key_cert {
+			image-id = <PLAT_KEY_CERT_ID>;
+			signing-key = <&prot_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			plat_pk: plat_pk {
+				oid = PLAT_PK_OID;
+			};
+		};
+
+		non_trusted_fw_content_cert: non_trusted_fw_content_cert {
+			image-id = <NON_TRUSTED_FW_CONTENT_CERT_ID>;
+			parent = <&plat_key_cert>;
+			signing-key = <&plat_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			nt_world_bl_hash: nt_world_bl_hash {
+				oid = NON_TRUSTED_WORLD_BOOTLOADER_HASH_OID;
+			};
+			nt_fw_config_hash: nt_fw_config_hash {
+				oid = NON_TRUSTED_FW_CONFIG_HASH_OID;
+			};
+		};
+
+#if defined(SPD_spmd)
+		sip_sp_content_cert: sip_sp_content_cert {
+			image-id = <SIP_SP_CONTENT_CERT_ID>;
+			parent = <&core_swd_key_cert>;
+			signing-key = <&core_swd_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			sp_pkg1_hash: sp_pkg1_hash {
+				oid = SP_PKG1_HASH_OID;
+			};
+			sp_pkg2_hash: sp_pkg2_hash {
+				oid = SP_PKG2_HASH_OID;
+			};
+			sp_pkg3_hash: sp_pkg3_hash {
+				oid = SP_PKG3_HASH_OID;
+			};
+			sp_pkg4_hash: sp_pkg4_hash {
+				oid = SP_PKG4_HASH_OID;
+			};
+		};
+
+		plat_sp_content_cert: plat_sp_content_cert {
+			image-id = <PLAT_SP_CONTENT_CERT_ID>;
+			parent = <&plat_key_cert>;
+			signing-key = <&plat_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			sp_pkg5_hash: sp_pkg5_hash {
+				oid = SP_PKG5_HASH_OID;
+			};
+			sp_pkg6_hash: sp_pkg6_hash {
+				oid = SP_PKG6_HASH_OID;
+			};
+			sp_pkg7_hash: sp_pkg7_hash {
+				oid = SP_PKG7_HASH_OID;
+			};
+			sp_pkg8_hash: sp_pkg8_hash {
+				oid = SP_PKG8_HASH_OID;
+			};
+		};
+#endif
+	};
+
+	images {
+		compatible = "arm, img-descs";
+
+		hw_config {
+			image-id = <HW_CONFIG_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&hw_config_hash>;
+		};
+
+		bl31_image {
+			image-id = <BL31_IMAGE_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&soc_fw_hash>;
+		};
+
+		soc_fw_config {
+			image-id = <SOC_FW_CONFIG_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&soc_fw_config_hash>;
+		};
+
+		rmm_image {
+			image-id = <RMM_IMAGE_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&rmm_hash>;
+		};
+
+		bl32_image {
+			image-id = <BL32_IMAGE_ID>;
+			parent = <&trusted_os_fw_content_cert>;
+			hash = <&tos_fw_hash>;
+		};
+
+		tos_fw_config {
+			image-id = <TOS_FW_CONFIG_ID>;
+			parent = <&trusted_os_fw_content_cert>;
+			hash = <&tos_fw_config_hash>;
+		};
+
+		bl33_image {
+			image-id = <BL33_IMAGE_ID>;
+			parent = <&non_trusted_fw_content_cert>;
+			hash = <&nt_world_bl_hash>;
+		};
+
+		nt_fw_config {
+			image-id = <NT_FW_CONFIG_ID>;
+			parent = <&non_trusted_fw_content_cert>;
+			hash = <&nt_fw_config_hash>;
+		};
+
+#if defined(SPD_spmd)
+		sp_pkg1 {
+			image-id = <SP_PKG1_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg1_hash>;
+		};
+
+		sp_pkg2 {
+			image-id = <SP_PKG2_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg2_hash>;
+		};
+
+		sp_pkg3 {
+			image-id = <SP_PKG3_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg3_hash>;
+		};
+
+		sp_pkg4 {
+			image-id = <SP_PKG4_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg4_hash>;
+		};
+
+		sp_pkg5 {
+			image-id = <SP_PKG5_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg5_hash>;
+		};
+
+		sp_pkg6 {
+			image-id = <SP_PKG6_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg6_hash>;
+		};
+
+		sp_pkg7 {
+			image-id = <SP_PKG7_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg7_hash>;
+		};
+
+		sp_pkg8 {
+			image-id = <SP_PKG8_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg8_hash>;
+		};
+#endif
+	};
+};
+
+non_volatile_counters: non_volatile_counters {
+	compatible = "arm, non-volatile-counter";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	cca_nv_ctr: cca_nv_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = CCA_FW_NVCOUNTER_OID;
+	};
+
+	trusted_nv_ctr: trusted_nv_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = TRUSTED_FW_NVCOUNTER_OID;
+	};
+
+	non_trusted_nv_ctr: non_trusted_nv_ctr {
+		id  = <NON_TRUSTED_NV_CTR_ID>;
+		oid = NON_TRUSTED_FW_NVCOUNTER_OID;
+	};
+};
+
+rot_keys {
+	swd_rot_pk: swd_rot_pk {
+		oid = SWD_ROT_PK_OID;
+	};
+	prot_pk: prot_pk {
+		oid = PROT_PK_OID;
+	};
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_undefined_parent.dtsi b/tools/cot_dt2c/tests/test_invalid_undefined_parent.dtsi
new file mode 100644
index 0000000..b761beb
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_undefined_parent.dtsi
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there
+ * are image/certificate that points to invalid parent
+ *
+ */
+
+#include <tools_share/cca_oid.h>
+#include <common/tbbr/tbbr_img_def.h>
+#include <common/nv_cntr_ids.h>
+
+cot {
+	manifests {
+		compatible = "arm, cert-descs";
+
+		cca_content_cert: cca_content_cert {
+			root-certificate;
+			image-id =<CCA_CONTENT_CERT_ID>;
+			antirollback-counter = <&cca_nv_ctr>;
+
+			tb_fw_hash: tb_fw_hash {
+				oid = TRUSTED_BOOT_FW_HASH_OID;
+			};
+			tb_fw_config_hash: tb_fw_config_hash {
+				oid = TRUSTED_BOOT_FW_CONFIG_HASH_OID;
+			};
+			hw_config_hash: hw_config_hash {
+				oid = HW_CONFIG_HASH_OID;
+			};
+			fw_config_hash: fw_config_hash {
+				oid = FW_CONFIG_HASH_OID;
+			};
+			soc_fw_hash: soc_fw_hash {
+				oid = SOC_AP_FW_HASH_OID;
+			};
+			soc_fw_config_hash: soc_fw_config_hash {
+				oid = SOC_FW_CONFIG_HASH_OID;
+			};
+			rmm_hash: rmm_hash {
+				oid = RMM_HASH_OID;
+			};
+		};
+
+		core_swd_key_cert: core_swd_key_cert {
+			root-certificate;
+			image-id = <CORE_SWD_KEY_CERT_ID>;
+			signing-key = <&swd_rot_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			core_swd_pk: core_swd_pk {
+				oid = CORE_SWD_PK_OID;
+			};
+		};
+
+		trusted_os_fw_content_cert: trusted_os_fw_content_cert {
+			image-id = <TRUSTED_OS_FW_CONTENT_CERT_ID>;
+			parent = <&core_swd_key_cert>;
+			signing-key = <&core_swd_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			tos_fw_hash: tos_fw_hash {
+				oid = TRUSTED_OS_FW_HASH_OID;
+			};
+			tos_fw_config_hash: tos_fw_config_hash {
+				oid = TRUSTED_OS_FW_CONFIG_HASH_OID;
+			};
+		};
+
+		plat_key_cert: plat_key_cert {
+			root-certificate;
+			image-id = <PLAT_KEY_CERT_ID>;
+			signing-key = <&prot_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			plat_pk: plat_pk {
+				oid = PLAT_PK_OID;
+			};
+		};
+
+		non_trusted_fw_content_cert: non_trusted_fw_content_cert {
+			image-id = <NON_TRUSTED_FW_CONTENT_CERT_ID>;
+			parent = <&wrong_parent>;
+			signing-key = <&plat_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			nt_world_bl_hash: nt_world_bl_hash {
+				oid = NON_TRUSTED_WORLD_BOOTLOADER_HASH_OID;
+			};
+			nt_fw_config_hash: nt_fw_config_hash {
+				oid = NON_TRUSTED_FW_CONFIG_HASH_OID;
+			};
+		};
+
+#if defined(SPD_spmd)
+		sip_sp_content_cert: sip_sp_content_cert {
+			image-id = <SIP_SP_CONTENT_CERT_ID>;
+			parent = <&wrong_parent>;
+			signing-key = <&core_swd_pk>;
+			antirollback-counter = <&trusted_nv_ctr>;
+
+			sp_pkg1_hash: sp_pkg1_hash {
+				oid = SP_PKG1_HASH_OID;
+			};
+			sp_pkg2_hash: sp_pkg2_hash {
+				oid = SP_PKG2_HASH_OID;
+			};
+			sp_pkg3_hash: sp_pkg3_hash {
+				oid = SP_PKG3_HASH_OID;
+			};
+			sp_pkg4_hash: sp_pkg4_hash {
+				oid = SP_PKG4_HASH_OID;
+			};
+		};
+
+		plat_sp_content_cert: plat_sp_content_cert {
+			image-id = <PLAT_SP_CONTENT_CERT_ID>;
+			signing-key = <&plat_pk>;
+			antirollback-counter = <&non_trusted_nv_ctr>;
+
+			sp_pkg5_hash: sp_pkg5_hash {
+				oid = SP_PKG5_HASH_OID;
+			};
+			sp_pkg6_hash: sp_pkg6_hash {
+				oid = SP_PKG6_HASH_OID;
+			};
+			sp_pkg7_hash: sp_pkg7_hash {
+				oid = SP_PKG7_HASH_OID;
+			};
+			sp_pkg8_hash: sp_pkg8_hash {
+				oid = SP_PKG8_HASH_OID;
+			};
+		};
+#endif
+	};
+
+	images {
+		compatible = "arm, img-descs";
+
+		hw_config {
+			image-id = <HW_CONFIG_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&hw_config_hash>;
+		};
+
+		bl31_image {
+			image-id = <BL31_IMAGE_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&soc_fw_hash>;
+		};
+
+		soc_fw_config {
+			image-id = <SOC_FW_CONFIG_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&soc_fw_config_hash>;
+		};
+
+		rmm_image {
+			image-id = <RMM_IMAGE_ID>;
+			parent = <&cca_content_cert>;
+			hash = <&rmm_hash>;
+		};
+
+		bl32_image {
+			image-id = <BL32_IMAGE_ID>;
+			parent = <&trusted_os_fw_content_cert>;
+			hash = <&tos_fw_hash>;
+		};
+
+		tos_fw_config {
+			image-id = <TOS_FW_CONFIG_ID>;
+			parent = <&trusted_os_fw_content_cert>;
+			hash = <&tos_fw_config_hash>;
+		};
+
+		bl33_image {
+			image-id = <BL33_IMAGE_ID>;
+			parent = <&non_trusted_fw_content_cert>;
+			hash = <&nt_world_bl_hash>;
+		};
+
+		nt_fw_config {
+			image-id = <NT_FW_CONFIG_ID>;
+			hash = <&nt_fw_config_hash>;
+		};
+
+#if defined(SPD_spmd)
+		sp_pkg1 {
+			image-id = <SP_PKG1_ID>;
+			hash = <&sp_pkg1_hash>;
+		};
+
+		sp_pkg2 {
+			image-id = <SP_PKG2_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg2_hash>;
+		};
+
+		sp_pkg3 {
+			image-id = <SP_PKG3_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg3_hash>;
+		};
+
+		sp_pkg4 {
+			image-id = <SP_PKG4_ID>;
+			parent = <&sip_sp_content_cert>;
+			hash = <&sp_pkg4_hash>;
+		};
+
+		sp_pkg5 {
+			image-id = <SP_PKG5_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg5_hash>;
+		};
+
+		sp_pkg6 {
+			image-id = <SP_PKG6_ID>;
+			parent = <&wrong_parent>;
+			hash = <&sp_pkg6_hash>;
+		};
+
+		sp_pkg7 {
+			image-id = <SP_PKG7_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg7_hash>;
+		};
+
+		sp_pkg8 {
+			image-id = <SP_PKG8_ID>;
+			parent = <&plat_sp_content_cert>;
+			hash = <&sp_pkg8_hash>;
+		};
+#endif
+	};
+};
+
+non_volatile_counters: non_volatile_counters {
+	compatible = "arm, non-volatile-counter";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	cca_nv_ctr: cca_nv_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = CCA_FW_NVCOUNTER_OID;
+	};
+
+	trusted_nv_ctr: trusted_nv_ctr {
+		id  = <TRUSTED_NV_CTR_ID>;
+		oid = TRUSTED_FW_NVCOUNTER_OID;
+	};
+
+	non_trusted_nv_ctr: non_trusted_nv_ctr {
+		id  = <NON_TRUSTED_NV_CTR_ID>;
+		oid = NON_TRUSTED_FW_NVCOUNTER_OID;
+	};
+};
+
+rot_keys {
+	swd_rot_pk: swd_rot_pk {
+		oid = SWD_ROT_PK_OID;
+	};
+	prot_pk: prot_pk {
+		oid = PROT_PK_OID;
+	};
+};
diff --git a/tools/cot_dt2c/tests/test_util.py b/tools/cot_dt2c/tests/test_util.py
new file mode 100644
index 0000000..b8e44d4
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_util.py
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import os
+import sys
+
+from cot_dt2c.cli import *
+from click.testing import CliRunner
+
+def get_script_path():
+    return os.path.dirname(os.path.realpath(sys.argv[0]))
+
+def test_convert():
+    runner = CliRunner()
+    test_file = get_script_path() + "/test.dtsi"
+    test_output = get_script_path() + "/test.c"
+
+    result = runner.invoke(convert_to_c, [test_file, test_output])
+    try:
+        assert result.output == ""
+    except:
+        print("test convert fail")
+
+    try:
+        os.remove(test_output)
+    except OSError:
+        pass
+
+if __name__=="__main__":
+    test_convert()