blob: 434fc0ca37190f216e413e32073359f9a48f06f5 [file] [log] [blame]
lowRISC Contributors802543a2019-08-31 12:12:56 +01001#!/usr/bin/env python3
2# Copyright lowRISC contributors.
3# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4# SPDX-License-Identifier: Apache-2.0
5r"""Top Module Generator
6"""
7import argparse
8import logging as log
9import sys
10from io import StringIO
11from pathlib import Path
12
13import hjson
14from mako.template import Template
15
16import tlgen
17from reggen import gen_rtl, gen_dv, validate
18from topgen import get_hjsonobj_xbars, merge_top, search_ips, validate_top
19
20# Filter from IP list but adding generated hjson
21filter_list = ['rv_plic', 'alert_h']
22
23# Common header for generated files
24genhdr = '''// Copyright lowRISC contributors.
25// Licensed under the Apache License, Version 2.0, see LICENSE for details.
26// SPDX-License-Identifier: Apache-2.0
27//
28// ------------------- W A R N I N G: A U T O - G E N E R A T E D C O D E !! -------------------//
29// PLEASE DO NOT HAND-EDIT THIS FILE. IT HAS BEEN AUTO-GENERATED WITH THE FOLLOWING COMMAND:
30'''
31
32def generate_rtl(top, tpl_filename):
33 top_rtl_tpl = Template(filename=tpl_filename)
34
35 out_rtl = top_rtl_tpl.render(top=top)
36 return out_rtl
37
38
39def generate_xbars(top, out_path):
40 for obj in top["xbar"]:
41 xbar = tlgen.validate(obj)
42
43 if not tlgen.elaborate(xbar):
44 log.error("Elaboration failed." + repr(xbar))
45
46 # Add clocks to the top configuration
47 obj["clocks"] = xbar.clocks
48 out_rtl, out_pkg, out_dv = tlgen.generate(xbar)
49
50 rtl_path = out_path / 'rtl'
51 rtl_path.mkdir(parents=True, exist_ok=True)
52 dv_path = out_path / 'dv'
53 dv_path.mkdir(parents=True, exist_ok=True)
54
55 rtl_filename = "xbar_%s.sv" % (xbar.name)
56 rtl_filepath = rtl_path / rtl_filename
57 with rtl_filepath.open(mode='w', encoding='UTF-8') as fout:
58 fout.write(out_rtl)
59
60 pkg_filename = "tl_%s_pkg.sv" % (xbar.name)
61 pkg_filepath = rtl_path / pkg_filename
62 with pkg_filepath.open(mode='w', encoding='UTF-8') as fout:
63 fout.write(out_pkg)
64
65 dv_filename = "xbar_%s_tb.sv" % (xbar.name)
66 dv_filepath = dv_path / dv_filename
67 with dv_filepath.open(mode='w', encoding='UTF-8') as fout:
68 fout.write(out_dv)
69
70
71def generate_plic(top, out_path):
72 # Count number of interrupts
73 src = sum([x["width"] if "width" in x else 1 for x in top["interrupt"]])
74
75 # Target and priority: Currently fixed
76 target = int(top["num_cores"], 0) if "num_cores" in top else 1
77 prio = 3
78
79 # Define target path
80 # rtl: rv_plic.sv & rv_plic_reg_pkg.sv & rv_plic_reg_top.sv
81 # doc: rv_plic.hjson
82 rtl_path = out_path / 'rtl'
83 rtl_path.mkdir(parents=True, exist_ok=True)
84 doc_path = out_path / 'doc'
85 doc_path.mkdir(parents=True, exist_ok=True)
86
87 # Generating IP top module script is not generalized yet.
88 # So, topgen reads template files from rv_plic directory directly.
89 # Next, if the ip top gen tool is placed in util/ we can import the library.
90 tpl_path = out_path / '../ip/rv_plic/doc'
91 hjson_tpl_path = tpl_path / 'rv_plic.tpl.hjson'
92 rtl_tpl_path = tpl_path / 'rv_plic.tpl.sv'
93
94 # Generate Register Package and RTLs
95 out = StringIO()
96 with hjson_tpl_path.open(mode='r', encoding='UTF-8') as fin:
97 hjson_tpl = Template(fin.read())
98 out = hjson_tpl.render(src=src, target=target, prio=prio)
99 log.info("RV_PLIC hjson: %s" % out)
100
101 if out == "":
102 log.error("Cannot generate interrupt controller config file")
103 return
104
105 hjson_gen_path = doc_path / "rv_plic.hjson"
106 gencmd = ("// util/topgen.py -t hw/top_earlgrey/doc/top_earlgrey.hjson --plic-only "
107 "-o hw/top_earlgrey/\n\n")
108 with hjson_gen_path.open(mode='w', encoding='UTF-8') as fout:
109 fout.write(genhdr + gencmd + out)
110
111 # Generate register RTLs (currently using shell execute)
112 # TODO: More secure way to gneerate RTL
113 hjson_obj = hjson.loads(out,
114 use_decimal=True,
115 object_pairs_hook=validate.checking_dict)
116 validate.validate(hjson_obj)
117 gen_rtl.gen_rtl(hjson_obj, str(rtl_path))
118
119 # Generate RV_PLIC Top Module
120 with rtl_tpl_path.open(mode='r', encoding='UTF-8') as fin:
121 rtl_tpl = Template(fin.read())
122 out = rtl_tpl.render(src=src, target=target, prio=prio)
123 log.info("RV_PLIC RTL: %s" % out)
124
125 if out == "":
126 log.error("Cannot generate interrupt controller RTL")
127 return
128
129 rtl_gen_path = rtl_path / "rv_plic.sv"
130 with rtl_gen_path.open(mode='w', encoding='UTF-8') as fout:
131 fout.write(genhdr + gencmd + out)
132
133
134def generate_top_ral(top, ip_objs, out_path):
135 # construct top ral block
136 top_block = gen_rtl.Block()
137 top_block.name = "chip"
138 top_block.base_addr = 0
139 top_block.width = int(top["datawidth"])
140
141 # add blocks
142 for ip_obj in ip_objs:
143 top_block.blocks.append(gen_rtl.json_to_reg(ip_obj))
144
145 # add memories
146 if "memory" in top.keys():
147 for item in list(top["memory"]):
148 mem = gen_rtl.Window()
149 mem.name = item["name"]
150 mem.base_addr = int(item["base_addr"], 0)
151 mem.limit_addr = int(item["base_addr"], 0) + int(item["size"], 0)
152 # TODO: need to add mem access info for memories in topcfg
153 mem.dvrights = "RW"
154 mem.n_bits = top_block.width
155 top_block.wins.append(mem)
156
157 # get sub-block base addresses from top cfg
158 for block in top_block.blocks:
159 for module in top["module"]:
160 if block.name == module["name"]:
161 block.base_addr = module["base_addr"]
162 break
163 # generate the top ral model with template
164 gen_dv.gen_ral(top_block, str(out_path))
165
166
167def main():
168 parser = argparse.ArgumentParser(prog="topgen")
169 parser.add_argument(
170 '--topcfg',
171 '-t',
172 required=True,
173 help="`top_{name}.hjson` file.")
174 parser.add_argument('--tpl', '-c', help="`top_{name}.tpl.sv` file.")
175 parser.add_argument(
176 '--outdir',
177 '-o',
178 help='''Target TOP directory.
179 Module is created under rtl/. (default: dir(topcfg)/..)
180 ''') # yapf: disable
181 parser.add_argument('--verbose', '-v', action='store_true', help="Verbose")
182
183 # Generator options: 'no' series. cannot combined with 'only' series
184 parser.add_argument(
185 '--no-top',
186 action='store_true',
187 help="If defined, topgen doesn't generate top_{name} RTLs.")
188 parser.add_argument(
189 '--no-xbar',
190 action='store_true',
191 help="If defined, topgen doesn't generate crossbar RTLs.")
192 parser.add_argument(
193 '--no-plic',
194 action='store_true',
195 help="If defined, topgen doesn't generate the interrup controller RTLs."
196 )
197 parser.add_argument(
198 '--no-gen-hjson',
199 action='store_true',
200 help='''If defined, the tool assumes topcfg as a generated hjson.
201 So it bypasses the validation step and doesn't read ip and
202 xbar configurations
203 ''')
204
205 # Generator options: 'only' series. cannot combined with 'no' series
206 parser.add_argument(
207 '--top-only',
208 action='store_true',
209 help="If defined, the tool generates top RTL only") # yapf:disable
210 parser.add_argument(
211 '--xbar-only',
212 action='store_true',
213 help="If defined, the tool generates crossbar RTLs only")
214 parser.add_argument(
215 '--plic-only',
216 action='store_true',
217 help="If defined, the tool generates RV_PLIC RTL and hjson only")
218 parser.add_argument(
219 '--hjson-only',
220 action='store_true',
221 help="If defined, the tool generates complete hjson only")
222 # Generator options: generate dv ral model
223 parser.add_argument(
224 '--top_ral',
225 '-r',
226 default=False,
227 action='store_true',
228 help="If set, the tool generates top level RAL model for DV")
229
230 args = parser.parse_args()
231
232 # check combinations
233 if args.top_ral:
234 args.hjson_only = True
235 args.no_top = True
236
237 if args.hjson_only:
238 args.no_gen_hjson = False
239
240 if (args.no_top or args.no_xbar or
241 args.no_plic) and (args.top_only or args.xbar_only or
242 args.plic_only):
243 log.error(
244 "'no' series options cannot be used with 'only' series options")
245 raise SystemExit(sys.exc_info()[1])
246
247 if not args.hjson_only and not args.tpl:
248 log.error(
249 "Template file can be omitted only if '--hjson-only' is true")
250 raise SystemExit(sys.exc_info()[1])
251
252 if args.verbose:
253 log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG)
254 else:
255 log.basicConfig(format="%(levelname)s: %(message)s")
256
257 if not args.outdir:
258 outdir = Path(args.topcfg).parent / ".."
259 log.info("TOP directory not given. Use %s", (outdir))
260 elif not Path(args.outdir).is_dir():
261 log.error("'--outdir' should point to writable directory")
262 raise SystemExit(sys.exc_info()[1])
263 else:
264 outdir = Path(args.outdir)
265
266 out_path = Path(outdir)
267
268 if not args.no_gen_hjson or args.hjson_only:
269 # load top configuration
270 try:
271 with open(args.topcfg, 'r') as ftop:
272 topcfg = hjson.load(ftop, use_decimal=True)
273 except ValueError:
274 raise SystemExit(sys.exc_info()[1])
275
276 # Sweep the IP directory and gather the config files
277 ip_dir = Path(__file__).parents[1] / 'hw/ip'
278 ips = search_ips(ip_dir)
279
280 # exclude rv_plic (to use top_earlgrey one) and
281 ips = [x for x in ips if not x.parents[1].name in filter_list]
282
283 # It may require two passes to check if the module is needed.
284 # TODO: first run of topgen will fail due to the absent of rv_plic.
285 # It needs to run up to amend_interrupt in merge_top function
286 # then creates rv_plic.hjson then run xbar generation.
287 hjson_dir = Path(args.topcfg).parent
288 ips.append(hjson_dir / 'rv_plic.hjson')
289
290 # load hjson and pass validate from reggen
291 try:
292 ip_objs = []
293 for x in ips:
294 # Skip if it is not in the module list
295 if not x.stem in [ip["type"] for ip in topcfg["module"]]:
296 log.info(
297 "Skip module %s as it isn't in the top module list" %
298 x.stem)
299 continue
300
301 obj = hjson.load(
302 x.open('r'),
303 use_decimal=True,
304 object_pairs_hook=validate.checking_dict)
305 if validate.validate(obj) != 0:
306 log.info("Parsing IP %s configuration failed. Skip" % x)
307 continue
308 ip_objs.append(obj)
309
310 except ValueError:
311 raise SystemExit(sys.exc_info()[1])
312
313 # Read the crossbars under the top directory
314 xbar_objs = get_hjsonobj_xbars(hjson_dir)
315
316 log.info("Detected crossbars: %s" %
317 (", ".join([x["name"] for x in xbar_objs])))
318
319 # TODO: Add validate
320 topcfg = validate_top(topcfg)
321
322 # TODO: Add conversion logic from top to top.complete.hjson
323 completecfg = merge_top(topcfg, ip_objs, xbar_objs)
324
325 genhjson_path = hjson_dir / ("top_%s.gen.hjson" % completecfg["name"])
326 gencmd = ("// util/topgen.py -t hw/top_earlgrey/doc/top_earlgrey.hjson --hjson-only "
327 "-o hw/top_earlgrey/\n")
328
329 if args.top_ral:
330 generate_top_ral(completecfg, ip_objs, out_path)
331 else:
332 genhjson_path.write_text(genhdr + gencmd +
333 hjson.dumps(completecfg, for_json=True))
334
335 if args.hjson_only:
336 log.info("hjson is generated. Exiting...")
337 sys.exit()
338
339 if args.no_gen_hjson:
340 # load top.complete configuration
341 try:
342 with open(args.topcfg, 'r') as ftop:
343 completecfg = hjson.load(ftop, use_decimal=True)
344 except ValueError:
345 raise SystemExit(sys.exc_info()[1])
346
347 # Generate PLIC
348 if not args.no_plic or args.plic_only:
349 generate_plic(completecfg, out_path)
350
351 # Generate xbars
352 if not args.no_xbar or args.xbar_only:
353 generate_xbars(completecfg, out_path)
354
355 # TODO: Get name from hjson
356 top_name = completecfg["name"]
357
358 if not args.no_top or args.top_only:
359 rtl_path = out_path / 'rtl'
360 rtl_path.mkdir(parents=True, exist_ok=True)
361 rtl_filepath = rtl_path / ("top_%s.sv" % (top_name))
362 out_rtl = generate_rtl(completecfg, args.tpl)
363
364 with rtl_filepath.open(mode='w', encoding='UTF-8') as fout:
365 fout.write(out_rtl)
366
367
368if __name__ == "__main__":
369 main()