CIRCT  20.0.0git
codegen.py
Go to the documentation of this file.
1 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
2 # See https://llvm.org/LICENSE.txt for license information.
3 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4 
5 # Code generation from ESI manifests to source code. C++ header support included
6 # with the runtime, though it is intended to be extensible for other languages.
7 
8 from typing import List, TextIO, Type, Optional
9 from .accelerator import AcceleratorConnection
10 from .esiCppAccel import ModuleInfo
11 from . import types
12 
13 import argparse
14 from pathlib import Path
15 import textwrap
16 import sys
17 
18 _thisdir = Path(__file__).absolute().resolve().parent
19 
20 
21 class Generator:
22  """Base class for all generators."""
23 
24  language: Optional[str] = None
25 
26  def __init__(self, conn: AcceleratorConnection):
27  self.manifestmanifest = conn.manifest()
28 
29  def generate(self, output_dir: Path, system_name: str):
30  raise NotImplementedError("Generator.generate() must be overridden")
31 
32 
34  """Generate C++ headers from an ESI manifest."""
35 
36  language = "C++"
37 
38  # Supported bit widths for lone integer types.
39  int_width_support = set([8, 16, 32, 64])
40 
41  def get_type_str(self, type: types.ESIType) -> str:
42  """Get the textual code for the storage class of a type.
43 
44  Examples: uint32_t, int64_t, CustomStruct."""
45 
46  if isinstance(type, (types.BitsType, types.IntType)):
47  if type.bit_width not in self.int_width_supportint_width_support:
48  raise ValueError(f"Unsupported integer width: {type.bit_width}")
49  if isinstance(type, (types.BitsType, types.UIntType)):
50  return f"uint{type.bit_width}_t"
51  return f"int{type.bit_width}_t"
52  raise NotImplementedError(f"Type '{type}' not supported for C++ generation")
53 
54  def get_consts_str(self, module_info: ModuleInfo) -> str:
55  """Get the C++ code for a constant in a module."""
56  const_strs: List[str] = [
57  f"static constexpr {self.get_type_str(const.type)} "
58  f"{name} = 0x{const.value:x};"
59  for name, const in module_info.constants.items()
60  ]
61  return "\n".join(const_strs)
62 
63  def write_modules(self, output_dir: Path, system_name: str):
64  """Write the C++ header. One for each module in the manifest."""
65 
66  for module_info in self.manifestmanifest.module_infos:
67  s = f"""
68  /// Generated header for {system_name} module {module_info.name}.
69  #pragma once
70  #include "types.h"
71 
72  namespace {system_name} {{
73  class {module_info.name} {{
74  public:
75  {self.get_consts_str(module_info)}
76  }};
77  }} // namespace {system_name}
78  """
79 
80  hdr_file = output_dir / f"{module_info.name}.h"
81  with open(hdr_file, "w") as hdr:
82  hdr.write(textwrap.dedent(s))
83 
84  def write_type(self, hdr: TextIO, type: types.ESIType):
85  if isinstance(type, (types.BitsType, types.IntType)):
86  # Bit vector types use standard C++ types.
87  return
88  raise NotImplementedError(f"Type '{type}' not supported for C++ generation")
89 
90  def write_types(self, output_dir: Path, system_name: str):
91  hdr_file = output_dir / "types.h"
92  with open(hdr_file, "w") as hdr:
93  hdr.write(
94  textwrap.dedent(f"""
95  // Generated header for {system_name} types.
96  #pragma once
97 
98  #include <cstdint>
99 
100  namespace {system_name} {{
101  """))
102 
103  for type in self.manifestmanifest.type_table:
104  try:
105  self.write_typewrite_type(hdr, type)
106  except NotImplementedError:
107  sys.stderr.write(
108  f"Warning: type '{type}' not supported for C++ generation\n")
109 
110  hdr.write(
111  textwrap.dedent(f"""
112  }} // namespace {system_name}
113  """))
114 
115  def generate(self, output_dir: Path, system_name: str):
116  self.write_typeswrite_types(output_dir, system_name)
117  self.write_moduleswrite_modules(output_dir, system_name)
118 
119 
120 def run(generator: Type[Generator] = CppGenerator,
121  cmdline_args=sys.argv) -> int:
122  """Create and run a generator reading options from the command line."""
123 
124  argparser = argparse.ArgumentParser(
125  description=f"Generate {generator.language} headers from an ESI manifest",
126  formatter_class=argparse.RawDescriptionHelpFormatter,
127  epilog=textwrap.dedent("""
128  Can read the manifest from either a file OR a running accelerator.
129 
130  Usage examples:
131  # To read the manifest from a file:
132  esi-cppgen --file /path/to/manifest.json
133 
134  # To read the manifest from a running accelerator:
135  esi-cppgen --platform cosim --connection localhost:1234
136  """))
137 
138  argparser.add_argument("--file",
139  type=str,
140  default=None,
141  help="Path to the manifest file.")
142  argparser.add_argument(
143  "--platform",
144  type=str,
145  help="Name of platform for live accelerator connection.")
146  argparser.add_argument(
147  "--connection",
148  type=str,
149  help="Connection string for live accelerator connection.")
150  argparser.add_argument(
151  "--output-dir",
152  type=str,
153  default="esi",
154  help="Output directory for generated files. Recommend adding either `esi`"
155  " or the system name to the end of the path so as to avoid header name"
156  "conflicts. Defaults to `esi`")
157  argparser.add_argument(
158  "--system-name",
159  type=str,
160  default="esi_system",
161  help="Name of the ESI system. For C++, this will be the namespace.")
162 
163  if (len(cmdline_args) <= 1):
164  argparser.print_help()
165  return 1
166  args = argparser.parse_args(cmdline_args[1:])
167 
168  if args.file is not None and args.platform is not None:
169  print("Cannot specify both --file and --platform")
170  return 1
171 
172  conn: AcceleratorConnection
173  if args.file is not None:
174  conn = AcceleratorConnection("trace", f"-:{args.file}")
175  elif args.platform is not None:
176  if args.connection is None:
177  print("Must specify --connection with --platform")
178  return 1
179  conn = AcceleratorConnection(args.platform, args.connection)
180  else:
181  print("Must specify either --file or --platform")
182  return 1
183 
184  output_dir = Path(args.output_dir)
185  if output_dir.exists() and not output_dir.is_dir():
186  print(f"Output directory {output_dir} is not a directory")
187  return 1
188  if not output_dir.exists():
189  output_dir.mkdir(parents=True)
190 
191  gen = generator(conn)
192  gen.generate(output_dir, args.system_name)
193  return 0
194 
195 
196 if __name__ == '__main__':
197  sys.exit(run())
str get_type_str(self, types.ESIType type)
Definition: codegen.py:41
str get_consts_str(self, ModuleInfo module_info)
Definition: codegen.py:54
def generate(self, Path output_dir, str system_name)
Definition: codegen.py:115
def write_type(self, TextIO hdr, types.ESIType type)
Definition: codegen.py:84
def write_modules(self, Path output_dir, str system_name)
Definition: codegen.py:63
def write_types(self, Path output_dir, str system_name)
Definition: codegen.py:90
def __init__(self, AcceleratorConnection conn)
Definition: codegen.py:26
def generate(self, Path output_dir, str system_name)
Definition: codegen.py:29
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
Definition: codegen.py:121