11#!/usr/bin/env python3
22
33# Copyright (c) 2024 Rivos, Inc.
4+ # Copyright (c) 2025 lowRISC contributors.
45# SPDX-License-Identifier: Apache2
56
67"""OpenTitan QEMU configuration file generator.
1112from argparse import ArgumentParser
1213from configparser import ConfigParser
1314from logging import getLogger
14- from os .path import dirname , isdir , isfile , join as joinpath , normpath
15+ from os .path import abspath , dirname , isdir , isfile , join as joinpath , normpath
1516from traceback import format_exc
1617from typing import Optional
1718import re
@@ -52,12 +53,19 @@ def __init__(self):
5253 self ._roms : dict [Optional [int ], dict [str , str ]] = {}
5354 self ._otp : dict [str , str ] = {}
5455 self ._lc : dict [str , str ] = {}
56+ self ._top_name : Optional [str ] = None
57+
58+ @property
59+ def top_name (self ) -> Optional [str ]:
60+ """Return the name of the top as defined in a configuration file."""
61+ return self ._top_name
5562
5663 def load_top_config (self , toppath : str ) -> None :
5764 """Load data from HJSON top configuration file."""
5865 assert not _HJSON_ERROR
5966 with open (toppath , 'rt' ) as tfp :
6067 cfg = hjload (tfp )
68+ self ._top_name = cfg .get ('name' )
6169 for module in cfg .get ('module' ) or []:
6270 modtype = module .get ('type' )
6371 if modtype == 'rom_ctrl' :
@@ -223,17 +231,20 @@ def _generate_life_cycle(self, cfg: ConfigParser,
223231def main ():
224232 """Main routine"""
225233 debug = True
226- default_top = 'Darjeeling'
234+ top_map = {
235+ 'darjeeling' : 'dj' ,
236+ 'earlgrey' : 'eg' ,
237+ }
227238 try :
228239 desc = sys .modules [__name__ ].__doc__ .split ('.' , 1 )[0 ].strip ()
229240 argparser = ArgumentParser (description = f'{ desc } .' )
230241 files = argparser .add_argument_group (title = 'Files' )
231- files .add_argument ('opentitan' , nargs = 1 , metavar = 'TOPDIR' ,
232- help = 'OpenTitan top directory' )
242+ files .add_argument ('opentitan' , nargs = '?' , metavar = 'OTDIR' ,
243+ help = 'OpenTitan root directory' )
244+ files .add_argument ('-T' , '--top' , choices = top_map .keys (),
245+ help = 'OpenTitan top name' )
233246 files .add_argument ('-o' , '--out' , metavar = 'CFG' ,
234247 help = 'Filename of the config file to generate' )
235- files .add_argument ('-T' , '--top' , default = default_top ,
236- help = f'OpenTitan Top name (default: { default_top } )' )
237248 files .add_argument ('-c' , '--otpconst' , metavar = 'SV' ,
238249 help = 'OTP Constant SV file (default: auto)' )
239250 files .add_argument ('-l' , '--lifecycle' , metavar = 'SV' ,
@@ -254,29 +265,60 @@ def main():
254265 args = argparser .parse_args ()
255266 debug = args .debug
256267
257- configure_loggers (args .verbose , 'cfggen' , 'otp' )
268+ log = configure_loggers (args .verbose , 'cfggen' , 'otp' )[ 0 ]
258269
259270 if _HJSON_ERROR :
260271 argparser .error ('Missing HJSON module: {_HJSON_ERROR}' )
261272
262- topdir = args .opentitan [0 ]
263- if not isdir (topdir ):
264- argparser .error ('Invalid OpenTitan top directory' )
265- ot_dir = normpath (topdir )
266- top = f'top_{ args .top .lower ()} '
267- if args .top .lower () != default_top .lower ():
268- var = '' .join (w [0 ]
269- for w in camel_to_snake_case (args .top ).split ('_' ))
270- else :
271- var = 'dj'
273+ cfg = OtConfiguration ()
272274
273- if not args .topcfg :
274- cfgpath = joinpath (ot_dir , f'hw/{ top } /data/autogen/{ top } .gen.hjson' )
275+ topcfg = args .topcfg
276+ ot_dir = args .opentitan
277+ if not topcfg :
278+ if not args .opentitan :
279+ argparser .error ('OTDIR is required is no top file is specified' )
280+ if not isdir (ot_dir ):
281+ argparser .error ('Invalid OpenTitan root directory' )
282+ ot_dir = abspath (ot_dir )
283+ if not args .top :
284+ argparser .error ('Top name is required if no top file is '
285+ 'specified' )
286+ top = f'top_{ args .top } '
287+ topvar = top_map [args .top ]
288+ topcfg = joinpath (ot_dir , f'hw/{ top } /data/autogen/{ top } .gen.hjson' )
289+ if not isfile (topcfg ):
290+ argparser .error (f"No such file '{ topcfg } '" )
291+ log .info ("Top config: '%s'" , topcfg )
292+ cfg .load_top_config (topcfg )
275293 else :
276- cfgpath = args .topcfg
277- if not isfile (cfgpath ):
278- argparser .error (f"No such file '{ cfgpath } '" )
279-
294+ if not isfile (topcfg ):
295+ argparser .error (f'No such top file: { topcfg } ' )
296+ cfg .load_top_config (topcfg )
297+ ltop = cfg .top_name
298+ if not ltop :
299+ argparser .error ('Unknown top name' )
300+ log .info ("Top: '%s'" , cfg .top_name )
301+ ltop = ltop .lower ()
302+ topvar = {k .lower (): v for k , v in top_map .items ()}.get (ltop )
303+ if not topvar :
304+ argparser .error (f'Unsupported top name: { cfg .top_name } ' )
305+ top = f'top_{ ltop } '
306+ if not ot_dir :
307+ check_dir = f'hw/{ top } /data'
308+ cur_dir = dirname (topcfg )
309+ while cur_dir :
310+ check_path = joinpath (cur_dir , check_dir )
311+ if isdir (check_path ):
312+ ot_dir = cur_dir
313+ break
314+ cur_dir = dirname (cur_dir )
315+ if not ot_dir :
316+ argparser .error ('Cannot find OT root directory' )
317+ elif not isdir (ot_dir ):
318+ argparser .error ('Invalid OpenTitan root directory' )
319+ ot_dir = abspath (ot_dir )
320+ log .info ("OT directory: '%s'" , ot_dir )
321+ log .info ("Variant: '%s'" , topvar )
280322 if not args .lifecycle :
281323 lcpath = joinpath (ot_dir , 'hw/ip/lc_ctrl/rtl/lc_ctrl_state_pkg.sv' )
282324 else :
@@ -293,10 +335,9 @@ def main():
293335 argparser .error (f"No such file '{ ocpath } '" )
294336
295337 cfg = OtConfiguration ()
296- cfg .load_top_config (cfgpath )
297338 cfg .load_lifecycle (lcpath )
298339 cfg .load_otp_constants (ocpath )
299- cfg .save (var , args .socid , args .count , args .out )
340+ cfg .save (topvar , args .socid , args .count , args .out )
300341
301342 except (IOError , ValueError , ImportError ) as exc :
302343 print (f'\n Error: { exc } ' , file = sys .stderr )
0 commit comments